Spec-Zone.ru › NumPy 2.0

Объяснения кода NumPy на C

Фанатизм состоит в том, чтобы удваивать усилия, когда вы забыли свою цель. — Джордж Сантаяна

Авторитет — это человек, который может рассказать вам больше о чём-то, чем вам действительно хочется знать. — Неизвестно

Эта страница пытается объяснить логику некоторых новых фрагментов кода. Цель этих объяснений состоит в том, чтобы позволить кому-то понять идеи, лежащие в основе реализации, несколько легче, чем просто разглядывать код. Возможно, таким образом, алгоритмы смогут быть улучшены, позаимствованы и/или оптимизированы больше людей.

Модель памяти

Один из фундаментальных аспектов ndarray заключается в том, что массив рассматривается как «блок» памяти, начинающийся с некоторого местоположения. Интерпретация этой памяти зависит от информации о шаге. Для каждой размерности в \(N\)-мерном массиве целое число (шаг) указывает, сколько байтов нужно пропустить, чтобы перейти к следующему элементу в этой размерности. Если у вас нет массива с одним сегментом, эта информация о шаге должна учитываться при прохождении по массиву. Написать код, принимающий шаги, несложно, вам просто нужно использовать char* указатели, потому что шаги задаются в единицах байтов. Также следует иметь в виду, что шаги не обязательно должны быть кратными размеру элемента. Кроме того, помните, что если количество измерений массива равно 0 (иногда это называется rank-0 массивом), то переменные шаги и размерности NULL.

Помимо структурной информации, содержащейся в членах strides и dimensions объекта PyArrayObject, флаги содержат важную информацию о том, как можно получить доступ к данным. В частности, флаг NPY_ARRAY_ALIGNED устанавливается, когда память находится на соответствующей границе в соответствии с типом данных массива. Даже если у вас есть смежный блок памяти, вы не можете просто предположить, что безопасно обратиться к указателю, специфичному для типа данных, к элементу. Только если установлен флаг NPY_ARRAY_ALIGNED, это безопасная операция. На некоторых платформах это будет работать, но на других, таких как Solaris, это вызовет ошибку шины. Также необходимо убедиться, что установлен флаг NPY_ARRAY_WRITEABLE, если вы планируете записывать в область памяти массива. Также возможно получить указатель на область памяти, которая недоступна для записи. Иногда запись в область памяти, когда флаг NPY_ARRAY_WRITEABLE не установлен, просто невежливо. В других случаях это может привести к сбою программы (например, область данных, которая является файлом с отображением памяти только для чтения).

Инкапсуляция типа данных

См. также

Объекты типов данных (dtype)

Тип данных — важная абстракция для ndarray. Операции будут обращаться к типу данных, чтобы предоставить необходимую функциональность для работы с массивом. Эта функциональность предоставляется в списке указателей на функции, на которые указывает член f структуры PyArray_Descr. Таким образом, количество типов данных можно расширить, просто предоставив структуру PyArray_Descr с подходящими указателями на функции в члене f. Для встроенных типов существуют некоторые оптимизации, которые обходят этот механизм, но смысл абстракции типа данных заключается в возможности добавления новых типов данных.

Один из встроенных типов данных, тип void, позволяет использовать произвольные структурированные типы, содержащие 1 или более полей в качестве элементов массива. Поле — это просто другой объект типа данных вместе со смещением в текущем структурированном типе. Для поддержки произвольно вложенных полей реализованы несколько рекурсивных реализаций доступа к типу данных для типа void. Типичным приёмом является цикл по элементам словаря и выполнение определённой операции, основанной на объекте типа данных, хранящемся по данному смещению. Эти смещения могут быть произвольными числами. Поэтому необходимо учесть возможность столкновения с несовпадающими данными, если это необходимо.

Итераторы N-мерных массивов

См. также

Итерация по массивам

Очень распространённой операцией во многих кодах NumPy является необходимость итерации по всем элементам общего N-мерного массива с шагами. Эта операция общего N-мерного цикла абстрагирована в понятии объекта-итератора. Для написания N-мерного цикла вам нужно только создать объект-итератор из ndarray, работать с членом dataptr структуры объекта-итератора и вызвать макрос PyArray_ITER_NEXT на объекте-итераторе, чтобы перейти к следующему элементу. Элемент next всегда находится в порядке C-contiguous.

Макрос работает, сначала рассматривая частные случаи C-contiguous, 1-D и 2-D, которые работают очень просто.

В общем случае итерация выполняется путём отслеживания списка счётчиков координат в объекте-итераторе. На каждой итерации последний счётчик координат увеличивается (начиная с 0). Если этот счётчик меньше, чем значение, на единицу меньшее размера массива в этой размерности (предварительно вычисленное и сохранённое значение), счётчик увеличивается, а член dataptr увеличивается на шаги в этой размерности, и макрос завершается. Если достигается конец размерности, счётчик последней размерности сбрасывается до нуля, а dataptr перемещается к началу этой размерности путём вычитания значения шага, умноженного на значение, на единицу меньшее, чем количество элементов в этой размерности (это также предварительно вычисляется и сохраняется в члене backstrides объекта-итератора). В этом случае макрос не завершается, но счётчик локальной размерности уменьшается, так что размерность, предшествующая последней, принимает роль последней размерности, и описанные выше проверки выполняются снова для предпоследней размерности. Таким образом, dataptr корректируется соответствующим образом для произвольных шагов.

Член coordinates структуры PyArrayIterObject сохраняет текущий N-мерный счётчик, если только подлежащий массив не является C-contiguous, в этом случае счёт координат пропускается. Член index структуры PyArrayIterObject отслеживает текущий плоский индекс итератора. Он обновляется макросом PyArray_ITER_NEXT.

Распространение

См. также

Распространение

В Numeric, предшественнике NumPy, распространение реализовывалось в нескольких строках кода, глубоко упрятанных в ufuncobject.c. В NumPy понятие распространения было абстрагировано, чтобы его можно было применять в нескольких местах. Распространением управляет функция PyArray_Broadcast. Эта функция требует передачи объекта PyArrayMultiIterObject (или его двоичного эквивалента). Объект PyArrayMultiIterObject отслеживает количество измерений после распространения, размеры в каждом измерении, а также общий размер результата распространения. Он также отслеживает количество массивов, участвующих в распространении, и указатель на итератор для каждого из них.

Функция PyArray_Broadcast использует уже определенные итераторы, чтобы определить форму массива после распространения в каждом измерении (для создания итераторов одновременно с распространением используется функция PyArray_MultiIterNew). Затем итераторы корректируются, чтобы каждый итератор думал, что он итерирует массив с размером после распространения. Это делается путем изменения количества измерений итератора и размера в каждом измерении. Это работает, потому что шаги итератора также корректируются. Распространение изменяет (или добавляет) только одномерные элементы. Для этих измерений переменная шагов просто устанавливается в 0, чтобы указатель данных для итератора по этому массиву не перемещался во время операции распространения по расширенному измерению.

Распространение всегда реализовывалось в Numeric с помощью шагов со значением 0 для расширенных измерений. В NumPy это делается точно так же. Существенная разница заключается в том, что теперь массив шагов отслеживается в объекте PyArrayIterObject, а итераторы, участвующие в результате распространения, отслеживаются в PyArrayMultiIterObject, а вызов PyArray_Broadcast реализует Общие правила распространения.

Массивные скаляры

См. также

Скаляры

Массивные скаляры представляют собой иерархию типов Python, которые позволяют однозначное соответствие между типом данных, хранящимся в массиве, и типом Python, который возвращается при извлечении элемента из массива. Исключением из этого правила были массивы объектов. Массивы объектов представляют собой гетерогенные коллекции произвольных объектов Python. При выборе элемента из массива объектов вы получаете исходный объект Python (а не скаляр массива объекта, который существует, но редко используется в практических целях).

Массивные скаляры также предлагают те же методы и атрибуты, что и массивы, с целью использования того же кода для поддержки произвольных измерений (включая 0-мерные). Массивные скаляры являются только для чтения (неизменяемыми), за исключением скаляра void, к которому также можно записывать, чтобы более естественно работало настройка полей массивов структуры (a[0]['f1'] = value).

Индексирование

См. также

Индексирование в ndarrays, Процедуры индексирования

Все операции индексирования Python arr[index] организуются путем подготовки индекса и определения типа индекса. Поддерживаемые типы индексов:

  • целое число
  • newaxis
  • срез
  • Ellipsis
  • массивы целых чисел/подобные массивам (расширенное)
  • булево (один булевый массив); если в качестве индекса или формы есть более одного булевого массива, не соответствующего точно, булевый массив будет преобразован в целочисленный массив.
  • 0-мерный булевый (и также целочисленный); 0-мерные булевы массивы — это особый случай, который нужно обрабатывать в коде расширенного индексирования. Они сигнализируют о том, что 0-мерный булевый массив должен был быть интерпретирован как целочисленный массив.

А также особый случай скалярного массива, сигнализирующий о том, что целочисленный массив интерпретировался как целочисленный индекс, что важно, потому что целочисленный индекс массива вызывает копирование, но игнорируется, если возвращается скаляр (полный целочисленный индекс). Подготовленный индекс гарантированно будет корректным, за исключением значений за пределами границ и ошибок распространения при расширенном индексировании. Это включает в себя добавление Ellipsis для неполных индексов, например, когда двухмерный массив индексируется одним целым числом.

Следующий шаг зависит от типа обнаруженного индекса. Если все измерения индексируются целым числом, возвращается или устанавливается скаляр. Один булевый массив индексирования вызовет специализированные булевы функции. Индексы, содержащие Ellipsis или срез, но без расширенного индексирования, всегда создают представление в старом массиве путем вычисления новых шагов и смещения памяти. Это представление затем может быть возвращено или, при присваивании, заполнено с помощью PyArray_CopyObject. Обратите внимание, что PyArray_CopyObject также может быть вызван для временных массивов в других ветвях для поддержки сложных присваиваний, когда массив имеет тип объекта dtype.

Расширенное индексирование

Наиболее сложным случаем является расширенное индексирование, которое может или не может сочетаться с типичным индексированием на основе представления. Здесь целочисленные индексы интерпретируются как индексы на основе представления. Прежде чем пытаться понять это, вы можете ознакомиться с его тонкостями. Код расширенного индексирования имеет три разные ветви и один особый случай:

  • Есть один массив индексов, и он, а также массив присваивания, могут быть итерированы тривиальным образом. Например, они могут быть непрерывными. Кроме того, массив индексов должен быть типа intp, а массив значений в присваиваниях — правильного типа. Это просто быстрый путь.
  • Есть только целочисленные индексы массива, так что подмассива не существует.
  • Индексирование на основе представления и расширенное индексирование смешаны. В этом случае индексирование на основе представления определяет набор подмассивов, которые объединяются расширенным индексированием. Например, arr[[1, 2, 3], :] создается путем вертикального объединения подмассивов arr[1, :], arr[2, :], и arr[3, :].
  • Существует подмассив, но у него ровно один элемент. Этот случай можно рассматривать так, как будто подмассива нет, но нужно проявить некоторую осмотрительность при настройке.

Определение, какой случай применим, проверка распространения и определение необходимого типа транспонирования выполняются в PyArray_MapIterNew. После настройки есть два случая. Если подмассива нет или он содержит только один элемент, итерация по подмассивам не нужна, и готовится итератор, который итерирует все массивы индексов а также массив результатов или значений. Если подмассив есть, готовятся три итератора. Один для массивов индексов, один для массива результатов или значений (за вычетом его подмассива) и один для подмассивов исходного и массива результатов/присваивания. Первые два итератора дают (или позволяют вычислить) указатели на начало подмассива, что позволяет перезапустить итерацию по подмассивам.

Когда расширенные индексы находятся рядом, может потребоваться транспонирование. Все необходимые транспонирования выполняются PyArray_MapIterSwapAxes и должны выполняться вызывающей стороной, если только PyArray_MapIterNew не запросил выделение результата.

После подготовки получение и установка относительно просты, хотя нужно учитывать разные режимы итерации. Если во время получения элемента есть только один массив индексов, проверка корректности индексов выполняется заранее. В противном случае это обрабатывается в самом внутреннем цикле для оптимизации.

Универсальные функции

См. также

Универсальные функции (ufunc), Основы универсальных функций (ufunc)

Универсальные функции — это вызываемые объекты, которые принимают \(N\) входных значений и производят \(M\) выходных значений, оборачивая базовые циклы по 1-й размерности, работающие поэлементно, в полные, простые в использовании функции, которые бесшовно реализуют векторизацию, проверку типов, буферизованное приведение типов и обработку аргументов результата. Новые универсальные функции обычно создаются на C, хотя есть механизм для создания ufunc из функций Python (frompyfunc). Пользователь должен предоставить цикл по 1-й размерности, реализующий базовую функцию, принимающую скалярные входные значения и помещающую результирующие скаляры в соответствующие слоты вывода, как объяснено в реализации.

Настройка

Каждый вызов ufunc включает некоторую накладную стоимость, связанную с настройкой вычисления. Практическое значение этой накладной стоимости заключается в том, что хотя фактическое вычисление ufunc очень быстрое, вы сможете написать код, специфичный для массивов и типов, который будет работать быстрее для малых массивов, чем ufunc. В частности, использование ufunc для выполнения многих вычислений над 0-мерными массивами будет медленнее, чем другие решения на основе Python (модуль scalarmath, тихо импортируемый, существует именно для того, чтобы массивам-скалярам придать вид и ощущения вычислений на основе ufunc с существенно меньшей накладной стоимостью).

При вызове ufunc необходимо выполнить множество действий. Информация, собранная в ходе этих операций настройки, хранится в объекте цикла. Этот объект цикла — структура C (которая может стать объектом Python, но не инициализируется как таковой, поскольку используется только во внутренней части). У этого объекта цикла есть структура, необходимая для использования с PyArray_Broadcast, чтобы векторизация обрабатывалась так же, как и в других частях кода.

В первую очередь, в потокоспецифическом глобальном словаре ищутся текущие значения размера буфера, маски ошибок и связанного с ней объекта ошибки. Состояние маски ошибок контролирует, что происходит при обнаружении условия ошибки. Следует отметить, что проверка флагов ошибок аппаратного обеспечения выполняется только после выполнения каждого цикла по 1-й размерности. Это означает, что если входные и выходные массивы являются непрерывными и имеют правильный тип, чтобы выполнялся единственный цикл по 1-й размерности, то флаги могут не проверяться до тех пор, пока не будут вычислены все элементы массива. Поиск этих значений в потокоспецифическом словаре занимает время, которое легко игнорировать для всех, кроме очень небольших массивов.

После проверки потокоспецифических глобальных переменных входные данные оцениваются, чтобы определить, как ufunc должен продолжить работу, и входные и выходные массивы строятся при необходимости. Любые входные данные, которые не являются массивами, преобразуются в массивы (с использованием контекста при необходимости). Отмечается, какие из входных данных являются скалярными (и поэтому преобразуются в 0-мерные массивы).

Далее, из имеющихся циклов по 1-й размерности для ufunc выбирается соответствующий цикл по 1-й размерности, основанный на типах входных массивов. Этот цикл по 1-й размерности выбирается путем попытки сопоставления сигнатуры типов данных входных данных с доступными сигнатурами. Сигнатуры, соответствующие встроенным типам, хранятся в члене ufunc.types структуры ufunc. Сигнатуры, соответствующие пользовательским типам, хранятся в связанном списке информации о функциях, где головной элемент хранится как CObject в словаре userloops, имеющем в качестве ключа номер типа данных (первый пользовательский тип в списке аргументов используется в качестве ключа). Сигнатуры просматриваются до тех пор, пока не будет найдена сигнатура, в которую можно безопасно преобразовать все входные массивы (игнорируя любые скалярные аргументы, которые не могут определить тип результата). Следствием этой процедуры поиска является то, что «более мелкие типы» должны располагаться ниже «более крупных типов», когда сигнатуры хранятся. Если цикл по 1-й размерности не найден, то сообщается об ошибке. В противном случае argument_list обновляется сохранённой сигнатурой — на случай необходимости приведения типов и для исправления типов выходных данных, предполагаемых циклом по 1-й размерности.

Если у ufunc 2 входных и 1 выходной аргумент, и второй входной аргумент — это массив Object, выполняется проверка специального случая, чтобы вернуть NotImplemented, если второй входной аргумент не является ndarray, имеет атрибут __array_priority__ и имеет специальный метод __r{op}__. Таким образом, Python получает сигнал, чтобы предоставить другому объекту возможность завершить операцию вместо использования общих вычислений объектов-массивов. Это позволяет (например) разреженным матрицам переопределять оператор умножения циклом по 1-й размерности.

Для входных массивов, меньших заданного размера буфера, создаются копии всех несмежных, несовместимых или не соответствующих байтовому порядку массивов, чтобы гарантировать, что для малых массивов используется один цикл. Затем создаются итераторы массивов для всех входных массивов, и полученный набор итераторов векторизуется в единую форму.

Затем обрабатываются выходные аргументы (если таковые имеются), и строятся любые отсутствующие выходные массивы. Если какой-либо предоставленный выходной массив не имеет правильный тип (или несовместим) и меньше размера буфера, то создается новый выходной массив со специальным флагом NPY_ARRAY_WRITEBACKIFCOPY. В конце функции вызывается PyArray_ResolveWritebackIfCopy, чтобы его содержимое было скопировано обратно в выходной массив. Затем обрабатываются итераторы для выходных аргументов.

Наконец, принимается решение о том, как выполнить механизм циклирования, чтобы гарантировать, что все элементы входных массивов объединяются для получения выходных массивов правильного типа. Вариантами выполнения циклирования являются одноцикловое выполнение (для :term`непрерывных`, совместимых и корректных по типу данных), выполнение с шагами (для несмежных, но всё ещё совместимых и корректных по типу данных) и буферизованное выполнение (для несовместимых или некорректных по типу данных). В зависимости от выбранного метода выполнения цикл настраивается и выполняется.

Вызов функции

Этот раздел описывает, как настраивается и выполняется базовый цикл вычислений универсальной функции для каждого из трёх типов выполнения. Если NPY_ALLOW_THREADS определено во время компиляции, то при отсутствии массивов объектов блокировка глобального интерпретатора Python (GIL) снимается перед вызовом циклов. Она восстанавливается при необходимости для обработки условий ошибки. Проверка флагов ошибок аппаратного обеспечения выполняется только после завершения цикла по 1-й размерности.

Один цикл

Это самый простой случай. Ufunc выполняется путем вызова базового цикла по 1-й размерности ровно один раз. Это возможно только в том случае, когда у нас есть совместимые данные правильного типа (включая байтовый порядок) для входных и выходных данных, и все массивы имеют одинаковые шаги (либо непрерывные, 0-мерные или 1-мерные). В этом случае цикл вычислений по 1-й размерности вызывается один раз для вычисления вычисления для всего массива. Обратите внимание, что флаги ошибок аппаратного обеспечения проверяются только после завершения всего вычисления.

Цикл с шагами

Когда входные и выходные массивы совместимы и имеют правильный тип, но шаги не равномерны (непрерывные и 2-мерные или выше), для вычисления используется вторая структура циклирования. Этот подход преобразует все итераторы входных и выходных аргументов для итерации по всем измерениям, кроме самого большого. Внутренний цикл затем обрабатывается базовым циклом вычислений по 1-й размерности. Внешний цикл — это стандартный цикл итерации по преобразованным итераторам. Флаги ошибок аппаратного обеспечения проверяются после завершения каждого цикла по 1-й размерности.

Буферизованный цикл

Это код, который обрабатывает ситуацию, когда входные и/или выходные массивы несовместимы или не соответствуют ожидаемому типу данных (включая перестановку байтов) базовым циклом по 1-й размерности. Массивы также считаются несмежными. Код работает очень похоже на цикл с шагами, за исключением того, что внутренний цикл по 1-й размерности модифицируется так, что предварительная обработка выполняется над входными данными, а пост-обработка — над выходными данными в блоках bufsize (где bufsize — параметр, устанавливаемый пользователем). Базовый цикл вычислений по 1-й размерности вызывается с данными, которые копируются (если это необходимо). В этом случае код настройки и цикла значительно сложнее, потому что он должен обрабатывать:

  • выделение памяти для временных буферов
  • определение того, следует ли использовать буферы для входных и выходных данных (несовместимые и/или неверного типа данных)
  • копирование и, возможно, приведение типов данных для любых входных или выходных данных, для которых необходимы буферы.
  • специальную обработку массивов Object таким образом, чтобы счётчики ссылок обрабатывались должным образом при копировании и/или приведении типов.
  • разбиение внутреннего цикла по 1-й размерности на блоки по bufsize (с возможным остатком).

Опять же, флаги ошибок аппаратного обеспечения проверяются в конце каждого цикла по 1-й размерности.

Конечная обработка вывода

Ufuncs позволяют беспрепятственно передавать другие массивоподобные классы через интерфейс, при этом входные данные определенного класса будут вызывать выходные данные того же самого класса. Механизм работы следующий. Если какие-либо из входных данных не являются ndarrays и определяют метод __array_wrap__, то класс с наибольшим атрибутом __array_priority__ определяет тип всех выходных данных (за исключением каких-либо выходных массивов, переданных в качестве аргумента). Метод __array_wrap__ входного массива будет вызван с ndarray, возвращенным ufunc, в качестве входного параметра. Поддерживаются два стиля вызова функции __array_wrap__. Первый принимает ndarray в качестве первого аргумента и кортеж «контекста» во втором аргументе. Контекст — это (ufunc, аргументы, номер выходного аргумента). Это первый вызов, который будет предпринят. Если произойдет TypeError, функция вызывается только с ndarray в качестве первого аргумента.

Методы

Существует три метода ufuncs, которые требуют вычислений, аналогичных универсальным ufuncs. Это ufunc.reduce, ufunc.accumulate и ufunc.reduceat. Каждый из этих методов требует команды настройки, за которой следует цикл. Для методов возможны четыре стиля циклов: без элементов, один элемент, с шагом и с буферизацией. Это те же основные стили циклов, что и для вызова функций общего назначения, за исключением случаев без элементов и одного элемента, которые являются специальными случаями, возникающими, когда объекты входного массива имеют 0 и 1 элемент соответственно.

Настройка

Функция настройки для всех трех методов — construct_reduce. Эта функция создает объект цикла для сокращения и заполняет его параметрами, необходимыми для завершения цикла. Все методы работают только с ufuncs, принимающими 2 входа и возвращающими 1 выход. Следовательно, выбирается базовый 1-мерный цикл, предполагая сигнатуру [otype, otype, otype], где otype — запрашиваемый тип данных сокращения. Затем размер буфера и обработка ошибок извлекаются из глобального хранилища (по потокам). Для небольших массивов, которые не выровнены или имеют неправильный тип данных, создается копия, чтобы использовать небуферизованный участок кода. Затем выбирается стратегия циклирования. Если в массиве 1 элемент или 0 элементов, выбирается простой метод циклирования. Если массив не невыровнен и имеет правильный тип данных, выбирается циклирование с шагом. В противном случае необходимо выполнить буферизованное циклирование. Затем устанавливаются параметры циклирования, и создается массив возврата. Массив вывода имеет другое размерность, в зависимости от того, является ли метод reduce, accumulate или reduceat. Если выходной массив уже предоставлен, проверяется его размерность. Если выходной массив не является C-непрерывным, выровненным и имеет правильный тип данных, создается временная копия с установленным флагом NPY_ARRAY_WRITEBACKIFCOPY. Таким образом, методы смогут работать с корректным выходным массивом, но результат будет скопирован обратно в истинный выходной массив при вызове PyArray_ResolveWritebackIfCopy по завершении функции. Наконец, инициализируются итераторы для циклирования по соответствующей оси (в зависимости от значения оси, предоставленного методу), и процедура настройки возвращает в фактическую процедуру вычислений.

Reduce

Все методы ufunc используют одни и те же базовые 1-мерные вычислительные циклы с входными и выходными аргументами, скорректированными таким образом, чтобы выполнялось соответствующее сокращение. Например, ключом к функционированию reduce является то, что 1-мерный цикл вызывается с выходом и вторым входом, указывающим на одну и ту же позицию в памяти, и оба имеют шаг 0. Первый вход указывает на входной массив с шагом, заданным соответствующим шагом для выбранной оси. Таким образом, выполняемая операция

\begin{align*} o & = & i[0] \\ o & = & i[k]\textrm{<op>}o\quad k=1\ldots N \end{align*}

где \(N+1\) — количество элементов во входных данных \(i\), \(o\) — выход, а \(i[k]\) — \(k^{\textrm{th}}\) элемент \(i\) вдоль выбранной оси. Эта базовая операция повторяется для массивов с более чем 1 измерением, чтобы сокращение выполнялось для каждого 1-мерного подмассива вдоль выбранной оси. Итератор с удаленным выбранным измерением обрабатывает это циклирование.

Для буферизованных циклов необходимо позаботиться о копировании и преобразовании данных до вызова функции цикла, потому что базовый цикл ожидает выровненные данные правильного типа данных (включая порядок байтов). Буферизованный цикл должен обрабатывать это копирование и преобразование перед вызовом функции цикла для кусков размером не более заданного пользователем bufsize.

Accumulate

Метод accumulate очень похож на метод reduce в том, что выход и второй вход оба указывают на выход. Разница в том, что второй вход указывает на память на один шаг позади текущего указателя выхода. Таким образом, выполняемая операция

\begin{align*} o[0] & = & i[0] \\ o[k] & = & i[k]\textrm{<op>}o[k-1]\quad k=1\ldots N. \end{align*}

Выход имеет такую же размерность, как и вход, и каждый 1-мерный цикл выполняет операции над \(N\) элементами, когда размерность по выбранной оси равна \(N+1\). Опять же, буферизованные циклы следят за тем, чтобы скопировать и преобразовать данные перед вызовом базового 1-мерного вычислительного цикла.

Reduceat

Функция reduceat является обобщением функций reduce и accumulate. Она реализует reduce над диапазонами входного массива, заданными индексами. Дополнительный аргумент индексов проверяется, чтобы убедиться, что каждый вход не слишком велик для входного массива по выбранному измерению, перед выполнением вычислений цикла. Реализация цикла обрабатывается с помощью кода, очень похожим на код reduce, повторяемого столько раз, сколько элементов в входном массиве индексов. В частности: первый указатель входных данных, переданный базовому 1-мерному вычислительному циклу, указывает на входной массив в правильном месте, указанном массивом индексов. Кроме того, указатель выхода и второй указатель входных данных, переданные базовому 1-мерному циклу, указывают на одну и ту же позицию в памяти. Размер 1-мерного вычислительного цикла фиксирован и равен разнице между текущим индексом и следующим индексом (если текущий индекс — последний индекс, то следующий индекс предполагается равным длине массива по выбранному измерению). Таким образом, 1-мерный цикл реализует reduce над указанными индексами.

Невыровненные или тип данных цикла, не соответствующий типу данных входных и/или выходных данных, обрабатываются с помощью буферизованного кода, где данные копируются во временной буфер и преобразуются в правильный тип данных, если необходимо, перед вызовом базовой 1-мерной функции. Временные буферы создаются размером (элементов) не больше, чем значение размера буфера, задаваемого пользователем. Таким образом, цикл должен быть достаточно гибким, чтобы вызывать базовый 1-мерный вычислительный цикл достаточно раз для завершения всего вычисления частями размером не более значения размера буфера.

© 2005–2024 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/2.0/dev/internals.code-explanations.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API