Spec-Zone.ru › NumPy 1.12

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

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

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

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

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

Один из фундаментальных аспектов ndarray заключается в том, что массив рассматривается как «блок» памяти, начинающийся с некоторого местоположения. Интерпретация этой памяти зависит от информации о шагах. Для каждой размерности в N-мерном массиве целое число (шаг) определяет, сколько байтов нужно пропустить, чтобы перейти к следующему элементу в этой размерности. Если у вас нет массива с одним сегментом, эта информация о шагах должна учитываться при прохождении по массиву. Написание кода, принимающего шаги, не сложно, просто необходимо использовать указатели (char *), так как шаги выражаются в байтах. Имейте в виду также, что шаги не обязательно должны быть кратны размеру элемента. Также помните, что если число размерностей массива равно 0 (иногда называется массивом ранга 0), то переменные strides и dimensions равны 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-непрерывном порядке. Макрос работает, сначала обращаясь к частным случаям C-непрерывных, 1-мерных и 2-мерных случаев, которые работают очень просто.

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

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

Вещание

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

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

Вещание всегда реализовывалось в Numeric с использованием нулевых шагов для расширенных размерностей. Это делается точно так же в NumPy. Большая разница заключается в том, что теперь массив шагов хранится в PyArrayIterObject, итераторы, участвующие в результате вещания, хранятся в PyArrayMultiIterObject, и вызов PyArray_BroadCast реализует правила вещания.

Массивоподобные скаляры

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

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

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

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

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

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

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

Advanced indexing

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

  • Существует один массив индексов, и он, а также массив присваивания, могут быть итерированы тривиально. Например, они могут быть непрерывными. Кроме того, массив индексов должен быть типа 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-D будет медленнее, чем другие решения на основе Python (существует модуль scalarmath, который импортируется неявно, для того, чтобы скаляры массивов имели внешний вид вычислений на основе ufunc с значительно уменьшенной накладной стоимостью).

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

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

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

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

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

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

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

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

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

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

Один цикл

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

Цикл со смещением

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

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

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

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

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

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

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

Методы

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

Настройка

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

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-мерного подмассива вдоль выбранной оси. Итератор со снятым выбранным измерением обрабатывает это циклирование.

Для буферизованных циклов необходимо позаботиться о копировании и преобразовании данных перед вызовом функции цикла, так как подлежащий цикл ожидает выровненные данные правильного типа данных (включая порядок байтов). Буферизованный цикл должен обработать это копирование и преобразование перед вызовом функции цикла для кусков не больше, чем указанный пользователем 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, который повторяется столько раз, сколько элементов в входном индексе. В частности: первый указатель входа, переданный подлежащему 1-мерному вычислительному циклу, указывает на входной массив в правильном месте, указанном массивом индексов. Кроме того, указатель выхода и второй указатель входа, переданные подлежащему 1-мерному циклу, указывают на одно и то же место в памяти. Размер 1-мерного вычислительного цикла фиксируется как разность между текущим индексом и следующим (когда текущий индекс — последний, следующий индекс предполагается равным длине массива по выбранному измерению). Таким образом, 1-мерный цикл реализует редукцию над указанными индексами.

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

© 2008–2017 NumPy Developers
Licensed under the NumPy License.
https://docs.scipy.org/doc/numpy-1.12.0/reference/internals.code-explanations.html

Spec-Zone.ru

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