Spec-Zone.ru › NumPy 1.14

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

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

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

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

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

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

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

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

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

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

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

Очень распространённой операцией во многих кодах NumPy является необходимость итерирования по всем элементам общего N-мерного массива с шагами. Эта операция общего N-мерного цикла абстрагируется в понятии объекта итератора. Для написания N-мерного цикла достаточно создать объект итератора из ndarray, работать с членом dataptr структуры объекта итератора и вызвать макрос PyArray_ITER_NEXT (it) для объекта итератора, чтобы перейти к следующему элементу. «Следующий» элемент всегда находится в порядке C-contiguous. Макрос работает, сначала учитывая случаи C-contiguous, 1-D и 2-D, которые работают очень просто.

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

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

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

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

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

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

Масштабируемые массивы

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

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

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

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

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

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

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

Продвинутое индексирование

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

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

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

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

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

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

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

Настройка

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

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

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

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

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

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

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

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

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

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

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

Один цикл

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

Цикл с шагом

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

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

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

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

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

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

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

Методы

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

Настройка

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

Reduce

Все методы ufunc используют одни и те же внутренние вычислительные циклы по 1-D с входными и выходными аргументами, скорректированными для выполнения соответствующего сокращения. Например, ключ к работе reduce заключается в том, что цикл по 1-D вызывается с выводом и вторым входом, указывающими на одну и ту же позицию в памяти, и оба имеющие шаг 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-D подмассива вдоль выбранной оси. Итератор, у которого удалено выбранное измерение, обрабатывает этот цикл.

Для циклов с буфером необходимо позаботиться о копировании и преобразовании данных перед вызовом функции цикла, так как внутренний цикл ожидает выровненные данные правильного типа данных (включая порядок байтов). Цикл с буфером должен обрабатывать это копирование и преобразование до вызова функции цикла для блоков не больше, чем заданный пользователем параметр 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-D работает над N элементами, когда форма по выбранной оси составляет N+1. Опять же, циклы с буфером позаботятся о копировании и преобразовании данных до вызова внутреннего вычислительного цикла по 1-D.

Reduceat

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

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

© 2005–2019 NumPy Developers
Licensed under the 3-clause BSD License.
https://docs.scipy.org/doc/numpy-1.14.5/reference/internals.code-explanations.html

Spec-Zone.ru

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