Spec-Zone.ru › NumPy 1.11

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

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

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

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

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

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

Помимо структурной информации, содержащейся в членах шагов и измерений объекта 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 вещание всегда реализовывалось с использованием 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 также может быть вызван для временных массивов в других ветвях, чтобы поддерживать сложные присваивания, когда массив имеет тип объекта.

Advanced indexing

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

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

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

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

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

Universal Functions

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

Setup

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

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

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

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

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

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

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

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

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

Function call

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

One Loop

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

Strided Loop

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

Buffered Loop

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

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

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

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

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

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

Reduceat

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

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

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

Spec-Zone.ru

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