Julia: исключить (а затем удалить) обобщенную линейную индексацию

Созданный на 23 янв. 2016  ·  109Комментарии  ·  Источник: JuliaLang/julia

Я уверен, что это обсуждалось где-то в другом месте, но, возможно, не недавно или сфокусированно / изолированно. Если это не должен быть отчет о проблеме, я передам его пользователям julia.

По сути, я бы сказал, что такое поведение удивительно:

julia> x = reshape(1:3^3, (3,3,3));

julia> size(x)
(3,3,3)

julia> x[3,4]
12

Это было причиной недавно обнаруженной ошибки, из-за которой я случайно забыл индекс.
Я полагаю, что это в некотором роде обобщение того факта, что x[12] работает, но я не совсем уверен, почему и где это поведение полезно для индексов 2D или выше. Есть ли какая-то логика в текущем поведении?

РЕДАКТИРОВАТЬ: наиболее похожее обсуждение: https://github.com/JuliaLang/julia/issues/5396

arrays breaking help wanted

Самый полезный комментарий

Я ценю вашу озабоченность и то, что вы нашли время, чтобы выразить ее здесь. Перенос вещей на более ранние версии, безусловно, может быть проблемой.

Пункты 2–4 могут быть не строго техническими, но тогда дизайн языков программирования в целом не является строго техническим - это прикладная психология. Если бы люди были чисто техническими существами, нам не понадобились бы удобные интерфейсы. Проблема с 1 заключается в том, что любая сложность в стандартной библиотеке - постоянное бремя, которое нужно поддерживать и за которое нести ответственность - я не думаю, что эта функция тянет на себя вес: она не предлагает достаточно, чтобы оправдать свое присутствие в стандарте. библиотека.

Все 109 Комментарий

Последний индекс эффективно интерпретируется как линейный индекс, если размерность недостаточна. (Более точно, для LinearFast AbstractArrays все в конце преобразуется в линейный индекс, хотя есть промежуточная проверка границ.) Лишние единицы также отбрасываются. Так было «всегда», но я не буду закрывать этот вопрос, если вы надеетесь на повторное рассмотрение. Достаточно сказать, что я иногда находил такое поведение полезным, но я также понимаю, как оно могло быть источником ошибок.

Соответствующий код:
LinearFast (преобразует все в линейный индекс): https://github.com/JuliaLang/julia/blob/0c20e64ab2697302b419042b9a043e967ed3db53/base/abstractarray.jl#L508
LinearSlow (расширяется до полной размерности): https://github.com/JuliaLang/julia/blob/0c20e64ab2697302b419042b9a043e967ed3db53/base/abstractarray.jl#L525 -L551

Вопрос о том, будет ли эта функциональность полезной, не совсем такой, как о том, должно ли это быть поведение по умолчанию. Может быть функция linearindexing с таким поведением, или можно создать подмассив ...

FWIW, мне кажется, что такое поведение слишком странно, чтобы использоваться по умолчанию.

Я бы сказал, что ищу пересмотра, да. Я думаю, что это очень удивительно по сравнению с его (минимальной, но ненулевой) полезностью. Я предполагаю, что моей основной первоначальной целью было понять, насколько полезен он, и будет ли «исправление» иметь нетривиальные приложения производительности.

Что касается вариантов «исправления», после того, как я допускаю относительно небольшое рассмотрение, это не похоже ни на что, но требование всех размеров было бы согласованным. например, рассмотрим

julia> x = reshape(1:3^3, (3,3,3));
julia> x[3,3]
9

В некотором смысле я нахожу это даже более удивительным, чем мой другой пример (но я бы обнаружил, что он возвращает x[3,3,:] меньше).

Я согласен и твердо уверен, что индексирование массива с меньшим числом индексов, чем ранг массива, должно быть ошибкой. Это просто упрощает внесение трудных для поиска ошибок в код при использовании массивов более высокого ранга (скажем, 4-10 размерных). Достаточно просто использовать reshape, чтобы сделать ссылку без копирования, если кто-то хочет использовать функцию конечной линейной индексации (и это оставляет намерение ясным в коде). И можно было бы легко написать несколько вспомогательных функций, чтобы упростить использование конечной линейной индексации. Я также хотел бы отметить, что другие языки (например, IDL и Fortran) делают это ошибкой, поэтому нынешнее поведение будет удивительным для многих.

Случай одномерного линейного индексирования, возможно, подходит, поскольку индексирование как одномерного массива визуально отличается (по сравнению, например, с индексированием 8-мерного массива как 7-мерного). Однако, поскольку использовать vec просто, я не уверен, что даже это необходимо.

Это немного обсуждалось в https://github.com/JuliaLang/julia/issues/4774, но я не знаю, как ссылаться на конкретные комментарии, и это долгая проблема.

@BobPortmann: вы можете ссылаться на определенные комментарии, щелкнув правой кнопкой мыши по метке времени (рядом с именем автора в заголовке комментария) и выбрав «Копировать расположение ссылки» или эквивалент.

+1, чтобы пересмотреть это, кажется гораздо более вероятным, что это
использоваться по ошибке, чем намеренно.

Я думаю, что даже для линейного индексирования мы должны подумать, должно ли оно иметь
собственный метод вместо того, чтобы быть перегруженным при обычном индексировании.

Мне нравится идея дать линейной индексации специальный метод.

А как насчет A[i, LinearIndex(j)] ?

Я бы с этим согласился.

+1 за отказ от этого поведения и использование какого-либо отдельного синтаксиса для линейной индексации. Это поведение было просто выбрано по умолчанию, поскольку это то, что делают некоторые другие среды. Я бы назвал это чрезмерным обобщением преждевременной оптимизации.

Я бы назвал это чрезмерным обобщением преждевременной оптимизации.

Точно. Давайте откажемся от этого наверняка.

-1 за сброс 1D версии. Очень естественно перебирать все элементы в многомерном массиве, используя

for n=1:length(x)
  x[n] = ...
end

Я знаю, что этого можно добиться другими способами (например, перечислением), но вышеизложенное довольно легко запомнить.

@JeffBezanson , я не согласен с утверждением о чрезмерном обобщении преждевременной оптимизации.

Линейное индексирование - это _ естественная_ схема индексирования, и все, что обеспечивает многомерный доступ, на самом деле представляет собой некоторую инфраструктуру, облегчающую человеку написание кода или разработку алгоритма.
Полное устранение линейной индексации кажется мне неправильным, введение дополнительного синтаксиса (что для меня означает, что я должен нажимать больше клавиш в REPL, прежде чем получить вывод) кажется мне дополнительным бременем; a [i] просто линейно, a [i, j] размерно; в чем проблема?

Теперь к интересной проблеме, с которой началось это: наличие доступа aa [i, j], когда a фактически определено в 3D. Я не знал, что это существовало, но на самом деле это то, что я хотел бы иметь на языке.

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

На мой взгляд, линейная индексация - это естественная схема индексации в том же смысле, в каком машинный код является естественным языком программирования: он является фундаментальным.

Но Джулия занимается созданием абстракций. Фактически, он предоставляет такие абстракции, как подмассивы, где линейная индексация _не_ естественная схема индексации.

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

Просто подумайте вслух: в дополнение к eachindex может существовать функция eachelement которая возвращает ссылку на элемент массива, как в

for xn in eachelement(x)
    xn = ...
end

Эта ссылка может быть чем-то вроде нулевого подмассива.

Разве это не должно быть xn[] = ... ?

Чтобы просто получить доступ к значению, у нас уже есть это из for x in X... .

Верно, должно было быть xn[] .

Да, речь идет о том, чтобы вообще не обрабатывать индексы, если кто-то хочет перебирать весь массив, присваивая его элементам.

Это могло быть полезно.

: +1: отказаться от этого. Я раньше называл это «частичным» линейным индексированием - не уверен, где я взял эту терминологию. Это обсуждалось в https://github.com/JuliaLang/julia/issues/13015 и https://github.com/JuliaLang/julia/issues/5396 , и это пункт, который я перечислил как возможность в # 13157. .

Такой итератор был бы очень полезен для разреженных матриц, где доступ к неструктурным местоположениям не разрешен (например, к матрицам PETSc).

Мне просто интересно, как и зачем это вообще было внесено? Кто-то нашел это полезным, и я думаю, это происходило

Возможно, я предвзято, потому что мое кодирование в Matlab очень часто включает линейное индексирование, хотя данные организованы в 2 или 3D, и у меня есть ментальная модель, чтобы справиться с этим.

Еще -1 за отказ от 1D-версии по той же причине, что и https://github.com/JuliaLang/julia/issues/14770#issuecomment -174262352.

Просто оставьте 1D версию линейной индексации и удалите "частичную" линейную индексацию imo.

Я тоже хотел бы иметь версию 1d линейной индексации и избавиться от частичной линейной индексации. Это вполне разумное решение.

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

@toivoh , нет, я не за то, чтобы избавляться от этой частичной линейной индексации.

Вот почему я сказал почти всем :)
Есть ли еще кто-нибудь, кто хочет сохранить частичную линейную индексацию?

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

s / linear / partial linear / в последнем предложении.

Что ж, хотя я не хочу голосовать за определенное направление, есть варианты использования, и я даже использую его в некоторых ситуациях. Представьте, что у вас есть инструмент для построения графиков, способный отображать срезы в наборе 3D-данных c . Так будет отображаться

c[:,:,k]

где k - номер среза. Теперь я столкнулся с 4D данными (3D + время) и хотел бы повторно использовать существующую инфраструктуру. В настоящее время, когда все правильно набрано, будет очень просто протолкнуть набор данных 4D и разрешить k превышать size(c,3) .

В некотором коде Matlab у меня есть что-то вроде следующего. Мы оцениваем (для каждого временного шага / итерации) отношения AB в 2D-матрице NxN и записываем (для постпроцессора) эти NxN-матрицы в хранилище TxNxN 3D. Поскольку операция копирования основана на find (линейный индекс отчетов), в julia я мог бы сделать Tnn [time_step, find (ABrelation)> = cell_limit)] = 1; И да, я знаю, что могу написать это как явный цикл и не должен рассуждать с кодом Matlab для конструкций данных julia ...

@tknopp , допустимый пример с расширением размеров и попыткой сохранить инфраструктуру.

Мне кажется, что матрица Vector из NxN была бы лучшей структурой данных для этого варианта использования.

И да, я знаю, что могу написать это как явный цикл и не должен рассуждать с кодом Matlab для конструкций данных julia ...

Есть причина, почему что-то называется постобработкой -> во время записи вы можете не знать, что обрабатывать, поэтому мы выбираем однородные коллекции, чтобы потом свободно выбирать, с какими измерениями работать.

С подмассивом ваш код может быть записан как sub(Tnn, time_step)[find(ABrelation) >= cell_limit] = 1 . Это почти так же коротко и, возможно, яснее. Когда просмотры станут по умолчанию, они станут еще короче.

Еще +1 от меня за разрешение линейной индексации только для одномерного случая.

API find определенно являются одними из главных факторов, стоящих за поддержанием линейной индексации в целом. Они происходят из языков, для которых не было другого выхода.

Интересно, что как только вы начнете использовать подмассивы, вам вообще не понадобится find (при условии, что вы действительно имели в виду find(ABrelation .>= cell_limit) ):

sub(Tnn, time_step, :, :)[ABrelation .>= cell_limit] = 1

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

И напоследок, обратите внимание, как использование здесь частичной линейной индексации привело к тому, что ваш шаблон доступа стал строковым. Мы должны стремиться к API, которые поощряют оптимальные шаблоны доступа.

Линейное индексирование требует особого синтаксиса. Идея состоит в том, чтобы отловить ошибки, когда матрица случайно индексируется как вектор.

Возможности такого синтаксиса безграничны, и должна быть возможность найти синтаксис, который будет как кратким, так и лаконичным.

arr[:linear, i]
arr[Linear(i)]
linind(arr, i)

Кроме того, также возможно изменение формы массива или использование представления массива вне цикла.

может иметь смысл иметь операцию, которая представляет собой объединенную операцию reshape + getindex, чтобы избежать размещения измененной оболочки в куче

Хотите расширить, @carnaval? Я тоже размышлял об этом в виде итераторов «Янус», которые представляют два (или более) понятия местоположения. Я просто еще не решил, какие операции, кроме приращения, необходимы таким итераторам, и беспокоюсь, что поддержка смещений (сложение / вычитание) - минное поле.

В # 5396 я понял для себя, что использование частичной линейной индексации в Matlab заключается в том, чтобы иметь возможность делать let setindex! незаметно добавлять размеры к массивам. Поскольку julia не позволяет этого (уф!), Осталось не так много причин для частичной линейной индексации.

Все еще мне интересно, почему это вообще было здесь (не было времени копаться в журналах git)?

Я убежден (из приведенного выше обсуждения):
-1 к частичной линейной индексации
+1, чтобы линейное индексирование было доступно без дополнительного синтаксиса (одна запись в [v] должна рассказать историю)

Я за то, чтобы разрешить индексацию только с индексом для каждого измерения или только с одним индексом.

Я тоже за.

@carnaval Обозначение для этого может быть получено путем аннотирования параметров типа измерения, как в A{3,3}[i,j] и, возможно, A{}[i] для линейной индексации

Я предполагаю, что линейная индексация всегда будет использовать диапазон 1:length(A) .

Как это будет работать, например, для разреженных векторов? Априори невозможно различить A[regular_index] и A[linear_index] . Аналогично для векторов, нижняя граница которых не равна 1 . Может потребоваться специальный синтаксис (например, A[:linear, i] ).

Какую проблему вы пытаетесь решить с помощью дополнительного синтаксиса для линейной индексации?

Есть три:
(1) Учитывая матрицу M , случайное написание M[i] вместо M[i,j] , т.е. исключение индекса; со специальным синтаксисом для линейной индексации это было бы обнаружено.
(2) Если у вас есть вектор с отсчетом от нуля (не существует в Base, но удобен в некоторых случаях), то обычное индексирование начинается с 0, тогда как люди ожидают, что линейное индексирование всегда будет начинаться с 1. Без специального синтаксиса нет никакого способа различить эти два. То же самое верно для любого одномерного массива, который ведет себя иначе, чем Vector ; вы также можете представить себе «оконное» представление вектора, где индексирование не начинается с 1.
(3) Использование единственного линейного индекса вместо диапазонов индексов, объявленных с массивом, не является чем-то, что обычно доступно на многих языках (без явного создания такого представления), и, таким образом, будет запутывать новичков.

Я тоже считаю, что эта операция не очень распространена в «пользовательском» коде. Линейные индексы, вероятно, используются в основном в универсальных функциях, которые обрабатывают массивы независимо от их формы.

(2) довольно гипотетически. Любой массив M можно рассматривать как контейнер, и поэтому его можно перемещать с помощью M[1:length(M)] . Вот почему вообще существует length .

(3) Какие языки вы имеете в виду, но без этой функции? Конечно, здесь учитываются только языки со встроенными мульти-массивами.

(1) действительно ли нам нужно помогать каждому разработчику писать правильный код?

Для (2) см., Например, https://github.com/eschnett/F flexibleArrays.jl. Если вы определяете 1d-массив с диапазоном индексов 0:9 , то его индексирование с помощью M[1:length(M)] не работает. (Конечно, вы все еще хотите иметь возможность индексировать его таким образом, поэтому вам нужен специальный синтаксис.)

Что касается (3), моя точка зрения была «без явного представления точки зрения». Не имеет значения, являются ли массивы встроенными или определенными в библиотеке. Я думаю, например, о Fortran, C, C ++, Mathematica, Python. Все они допускают линейную индексацию, и для всех требуется специальный синтаксис или конструкция для использования линейной индексации с многомерным массивом.

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

(3) Хм, хорошо, только что видел, что Python / Numpy обрабатывает это по-другому. В C / C ++ нет многодимовых массивов в том смысле, в каком мы их используем в Julia.

Для чего стоит. Я думаю, что синтаксис имеет смысл, поскольку я рассматриваю массив как тип контейнера. И я много использовал линейную индексацию в «пользовательском» коде.

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

Итак, целые числа используются в качестве абстрактных маркеров для всех элементов массива.

Что касается C и C ++: double array[2][2] дает вам 2d-массив. Чтобы получить 1d представление, напишите (double*)&array[0][0] : это возможно, но не неявно.

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

Я не уверен, что для этого нужен особый синтаксис. Это все лишнее, о чем нужно помнить.

Я сам играл с некоторыми подобными контейнерами ... и пришел к выводу, что слишком опасно использовать подтип AbstractArray, если индексирование с целыми числами означает что-либо, кроме доступа к элементу внутри 1:size(A,d) . Существует слишком много методов абстрактных массивов, которые предполагают, что эти обращения являются внутренними, и впоследствии отключают проверку границ. eachindex действительно помогает, но не до конца.

Я думаю, что лучшая альтернатива, которую я нашел, - это использование настраиваемого типа индекса, который дополняет поведение индексации вашими настраиваемыми функциями. Как вы говорите, без специального синтаксиса или типа индекса невозможно отличить ваше пользовательское поведение индексирования от «обычного» индексирования. И в этом случае я думаю, что необычное поведение проигрывает в улучшении синтаксиса. Так работает AxisArrays.jl . Вы можете использовать нецелочисленные типы, чтобы разрешить индексацию по меткам оси, что может заставить его вести себя как массив смещения ( пример ; вместо того, чтобы полагаться на SIUnits, вы также можете создать свой собственный Integer тип).


Начнем с отказа от частичной линейной индексации и конечных одноэлементных измерений. Это довольно просто, но лучший способ сделать это зависит от # 11242… в противном случае нам пришлось бы (ab) использовать дополнительные функции. Мы можем легко добавить тип LinearIndex, если период устаревания окажется болезненным, но я был бы удивлен, если это необходимо.

Для # 11242 мне нужно найти время, чтобы отладить загадочные проблемы с производительностью, но в остальном я думаю, что это близко.

Начнем с отказа от частичной линейной индексации и конечных одноэлементных измерений.

Я согласен с частичной линейной индексацией, но я не уверен в конечных размерах одиночного элемента. Есть много алгоритмов, которые выглядят так:

function sumcolumns(A::AbstractVecOrMat)
    m, n = size(A, 1), size(A, 2)
    s = zeros(accum(eltype(A)), 1, n)
    for j = 1:n
        z = s[1,j]
        for i = 1:m
            z += A[i,j]
        end
        s[1,j] = z
    end
    s
end

что сломалось бы для Vector input, если бы мы избавились от конечных синглтонов.

Обработка векторов как матриц, похоже, исходит из предыстории Джулии в Matlab, и люди, похоже, пытаются отойти от нее, не теряя при этом удобства, которые она предлагает.

Таким образом, кажется, что некоторые люди действительно любят эту функцию (фон Matlab), в то время как другие не привыкли к ней и находят ее странной (фон Python / C / Fortran).

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

Недавно я реализовал векторы SIMD как тип данных https://github.com/eschnett/SIMD.jl , а затем мне пришла идея сделать их подтипами DenseArray{T,1} . К моему удивлению, это сломало примеры в моем Readme - REPL пробует выходные векторы как матрицы (точно так же, как вы описываете выше), заставляя меня поддерживать обработку векторов SIMD как 2d-массивов. SIMD-векторы по самой своей природе не многомерны; на самом деле их индексирование обычно довольно дорогое, а поддержка многомерного представления не будет полезна ни одному фактическому пользователю.

Итак - что же такое «абстрактный одномерный массив» AbstractVector в Джулии? Является ли это обобщением всех типов, подобных 1d-массиву, или он всегда по своей сути также является многомерным массивом? Если да, то должен ли быть другой абстрактный тип в Julia для случаев, когда 1d массивы всегда будут 1d

В настоящее время диапазоны можно рассматривать даже как матрицы: (1:10)[4,1] .

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

Часто это так, но в наши дни я обнаружил, что могу делать большинство многомерных вещей, просто используя декартовы итераторы. Если вы не знакомы с этим, AxisAlgorithms и base/reducedim.jl - хорошие модели.

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

Я пробовал декартовы итераторы. К сожалению, они ожидают в качестве аргументов целочисленные литералы, тогда как у меня есть параметр типа N . К тому времени, как параметр типа известен, макрос уже был развернут - следовательно, сгенерированная функция развернута достаточно поздно.

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

Теперь, когда я думаю об этом, у меня возникает ощущение, что это значительно упростило бы мой код.

Я думаю, вы путаете Base.Cartesian с CartesianIndex / CartesianRange. Если вы используете последний, макросов нет. В коде, который я связал, нет макросов, и вы все равно можете делать удивительные вещи: smile :.

Спасибо за указатель. Я не заметил разницы.

Есть ли способ перечислить в REPL все функции, которые имеют CartesianIndex или CartesianRange качестве ввода или вывода?

Я думаю, что все мои многомерные массивы должны поддерживать декартову индексацию.

Есть ли способ перечислить в REPL все функции, которые имеют CartesianIndex или CartesianRange в качестве ввода или вывода?

methodswith(CartesianIndex) то, что вы ищете? Лучший!

Ооо. Благодарю.

@eschnett , вы были не первым, кого эта разница смутила. Этого было достаточно, чтобы наконец заставить меня написать сообщение в блоге:
https://github.com/JuliaLang/julialang.github.com/pull/324

Текущее мнение таково, что мы должны отказаться от обобщенной линейной индексации.

@JeffBezanson , на основании чего?

@lobingera , если вы читаете эту https://github.com/JuliaLang/julia/issues/14770#issuecomment -174212684, поэтому в случае принятия вы ничего не потеряете.

Учитывая это, не могли бы вы подробнее рассказать о своей проблеме?

Есть ли план или предложение по линейной индексации для массивов, нижние границы которых не равны 1 ? Я бы хотел реализовать это для https://github.com/eschnett/F flexibleArrays.jl.

Моя текущая идея - определить тип immutable LinearIndex i::Int end и использовать его для линейной индексации, предполагая, что линейный индекс i=1 должен быть первым элементом массива.

Думаю, я бы попробовал использовать CartesianIndex для представления индексации в _shape_ массива вместо смещений памяти или других интерпретаций. Фактически, это то, что вектор LinearSlow в настоящее время будет производить с eachindex , даже если это может быть обычный старый диапазон. Это кажется немного назад, но может иметь смысл использовать CartesianIndexes также для представления линейной индексации. Линейное индексирование явно зависит от декартовой интерпретации ваших индексов.

Если у меня есть одномерный массив с диапазоном индекса 0:9 , как я могу использовать декартово индекс для представления линейного индекса? Очевидное значение CartesianIndex((1,)) уже представляет собой обычный индекс, указывающий на второй элемент, а CartesianIndex((10,)) уже является ошибкой за пределами допустимого диапазона.

Просто выделите getindex(A::FlexibleArray, ::CartesianIndex) чтобы получить нужную интерпретацию.

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

getindex(A::FlexibleVector, i::CartesianIndex{1}) = … # interpretation of index as 1:end
getindex(A::FlexibleArray, i::CartesianIndex{1}) = A[CartesianIndex(ind2sub(size(A), i[1]))]

?

Я уже хочу интерпретировать декартово индекс как обычный (зависящий от формы) индекс, поскольку декартовы индексы очень удобны для расчетов индексов.

Ага, мы говорим мимо друг друга. Когда я говорил выше о декартовой индексации, я имел в виду не только один или несколько индексов, но и то, что каждый индекс идет от 1:size(A, d) . Если вы хотите поддерживать линейную индексацию, вам нужно будет использовать специальный тип и реализовать специальные правила индексации, несмотря ни на что. Если вы просто используете CartesianIndex для универсального обозначения индексации на основе 1, тогда вам больше не нужно специализировать каждый индекс, и код, использующий явные CartesianRanges, по-прежнему будет работать. Лично я считаю, что это менее болезненный путь.

Особый тип и особые правила индексации не проблема (уже есть). Я ищу некоторую стандартизацию, которая работала бы и для других пакетов массивов, и, надеюсь, даже для Base.

Я предлагаю интерпретацию CartesianIndex которую мы уже предполагаем в Base: его индексы всегда основаны на 1. Согласившись с этой интерпретацией, ваши FlexibleArrays внезапно станут намного больше похожими на любой другой массив LinearSlow… и не требуют новых типов или идиом. Думаю, он станет еще лучше, если мы усовершенствуем наши идиомы итерации индекса. Я думаю, что это хорошая стандартизация.

Наличие CartesianIndex всегда начинающегося с 1 , не сильно отличается от формы, требующей, чтобы все индексы массива всегда начинались с 1 : это то, что у нас есть в Base, но есть случаи, когда это довольно неудобно.

CartesianIndex не только используется для eachindex , это очень удобный способ выполнять вычисления, не зависящие от ранга, для индексов массива. Если бы индексирование CartesianIndex всегда начиналось с 1 , тогда мне понадобился бы второй FlexibleCartesianIndex (с той же реализацией), для которого не было бы этого требования.

@timholy Ну ...
с моим недавним опытом, что происходит при отказе от языковых опций (или нового синтаксиса, или даже только переименования функций), я обеспокоен тем, что любое изменение языка или смена опций потребует усилий при переносе доступного кода. Да, я знаю, что мы все должны предполагать, что julia ломается до версии 1.0. Но это относится к правильному определению «мы». Существует растущее сообщество пользователей julia за пределами списка рассылки, github и таких мероприятий, как JuliaCon. И, например, список в пакетах (904 и растущий) с их точки зрения является чем-то вроде стандартной библиотеки.

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

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

Причина отказа от той или иной опции в одном из двух моментов в потоке: Неожиданное поведение. Для меня это не техническая причина.

@lobingera , насколько мне известно, это изменение полностью безопасно для устаревания. Я не совсем уверен, какое отношение это обсуждение имеет к векторизации и simd, оно касается удаления определенного синтаксиса, который может приводить к путанице и упрощает внесение ошибок.

Мы регулярно запускаем PkgEval для всей экосистемы пакета, чтобы оценить влияние изменений. Например, для изменения jb / functions

Что касается технических причин, чтобы избавиться от частичной линейной индексации:

  1. Это сложно, сложно реализовать правильно и еще сложнее проверить правильность.
  2. Это опасно в том смысле, что скрывает ошибки программирования.
  3. Это не нужно очень часто.
  4. Его можно заменить той же функциональностью с менее опасным синтаксисом.
  5. Синтаксис с меньшей вероятностью сбоев может быть предоставлен в пакете, а не в стандартной библиотеке.

Все эти моменты в сочетании с общим мнением о том, что это должно быть, кажется достаточным, чтобы отказаться от частичной линейной индексации в 0.5 и удалить ее в 0.6.

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

Если вы (все) считаете это необходимым для оптимизации версии 0.5, кто я такой, чтобы не согласиться?

Я ценю вашу озабоченность и то, что вы нашли время, чтобы выразить ее здесь. Перенос вещей на более ранние версии, безусловно, может быть проблемой.

Пункты 2–4 могут быть не строго техническими, но тогда дизайн языков программирования в целом не является строго техническим - это прикладная психология. Если бы люди были чисто техническими существами, нам не понадобились бы удобные интерфейсы. Проблема с 1 заключается в том, что любая сложность в стандартной библиотеке - постоянное бремя, которое нужно поддерживать и за которое нести ответственность - я не думаю, что эта функция тянет на себя вес: она не предлагает достаточно, чтобы оправдать свое присутствие в стандарте. библиотека.

Текущее мнение таково, что мы должны отказаться от обобщенной линейной индексации.

Есть ли у нас предлагаемый план реализации, как это сделать?

С # 11242 я думаю, что все должно быть довольно просто. Возможно, мы захотим представить новый метод sub2sub который похож на ind2sub но принимает кортеж с частичным линейным индексом вместо скалярного линейного индекса, чтобы обеспечить простую замену в метод устаревания.

getindex(::AbstractArray, ::Int) = # linear
getindex{T,N}(::AbstractArray{T,N}, ::Vararg{Int, N}) = # cartesian
<strong i="8">@deprecate</strong> getindex(A::AbstractArray, I::Int...) getindex(A, sub2sub(size(A), I)...)

Однако обратите внимание, что это _также_ устареет в конечных одноэлементных измерениях.

Заблокировано на https://github.com/JuliaLang/julia/pull/11242.

Я согласен с тем, что «обобщенная линейная индексация» сбивает с толку и нам, вероятно, лучше без нее. Однако при поиске вариантов использования trailingsize я нашел один пакет, в котором используется эта функция:

Было бы хорошо подумать, какой код заменит это.

Это огромная задача. Гайки и болты у него довольно простые, даже без # 11242. Просто добавьте depwarns в соответствующих случаях в abstractarray.jl и сделайте линейную быструю функцию сгенерированной функцией, чтобы сделать то же самое. Удалите эту строку, и все готово (при условии, что загрузка Windows больше не является проблемой).

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

@mbauman, у вас есть начало ветки или что-то еще, с чего мы могли бы начать? Если это просто вопрос устранения предупреждений об устаревании (но многих из них), это звучит легко для распространения, но кто еще может знать, как это начать?

Да, у меня есть эта ветка, но я могу обновить ее, чтобы использовать новый шикарный тип Vararg без особых хлопот.

Это неожиданно ... Любопытно посмотреть, где / что они, может быть, в ходе обсуждения что-то упустили

Мы могли бы справиться с устареванием, добавив LinearIndex(i) , которое разрешено только в последнем слоте. Наивно, что для этого, кажется, требуется Vararg в середине списка аргументов, но в наши дни мы можем организовать это либо с помощью сгенерированных функций, либо с помощью шифрованной комбинации Base.front и @inline ( за производительность).

Думаю, лучше просто использовать reshape в индексируемом массиве. Таким образом, он будет более производительным и для медленных линейных массивов. Может быть, нам нужен какой-то ярлык для выражения reshape A to have N dimensions , добавляя конечные одиночные измерения или свертывая последние измерения.

Это также хороший способ выразить алгоритм в DSP.jl, на который Стивен ссылался выше.

Где мы сейчас стоим на этом с объединенным # 16251? Это когда основная часть изменения проста, а остальное - вопрос охоты за устареванием, в которую могут вмешаться несколько человек?

Да, теперь это должно быть довольно просто.

  • Переместите методы, которые я пометил как устаревшие, и добавьте предупреждения об устаревании. В моем быстром тесте, приведенном выше, я заметил некоторую нестабильность с depwarn в ответе, потому что он полагается на стек ... и getindex встраивается как сумасшедший, поэтому стека просто не так много. Так что это может потребовать некоторого внимания.
  • Не упустите неожиданное рассогласование между методами вида getindex(A::Array, I::Int…) и getindex{T,N}(A::Array{T,N}, I::Vararg{Int, N}) . Возможно, вам потребуется добавить несколько явных нерекомендуемых функций, чтобы сохранить поведение, которое мы не хотим не рекомендовать. Я бы все равно поместил их в deprecations.jl , поскольку они будут необходимы только до тех пор, пока существуют устаревшие версии. Немного раздражает, но не очень сложно.
  • Пройдитесь по базе и тестам, добавляя повсюду изменения, чтобы соответствовать количеству индексов и осчастливить Юлию: reshape(A, Val{3})[i, j, k] .

Здесь нужно, чтобы кто-то (у @tkelman не было времени) начал это, внося изменения, предложенные @mbauman , даже если тесты не пройдут. Другие могут продолжить, исправляя различные тесты и другие способы использования обобщенной линейной индексации.

Решение по вызову для сортировки - мы можем отложить его до тех пор, пока у нас не будет времени, чтобы сделать это полностью.

Как бы то ни было, для наиболее простой реализации здесь требуется # 18457, поскольку он исправляет ошибку сортировки методов с Varargs с привязанной длиной.

Поскольку # 18457 почти готов (можем ли мы его еще объединить?), Давайте подождем и сделаем это.

Здесь необходимо обновление. В этой беседе мы говорили о двух совершенно разных вещах с совершенно разными ответвлениями:

  1. Не рекомендуется линеаризация любого размера, кроме первого. Это то, что я сделал в # 20079. Обратите внимание, что он по-прежнему позволяет индексацию в размерный массив N с меньшим, чем N индексами. Как только это изменение произойдет, мы сможем упростить понижение A[i, end] чтобы просто использовать size(A, 2) - и я думаю, что мы можем вообще отказаться от Base.trailingsize . Точно так же нам больше не нужно возиться с особыми случаями для завершения : s за пределами единственного случая A[:] . Примечательно, что это повлияло только на тестовый код, специально созданный для проверки такого поведения.
  2. Устарело индексирование в размерных массивах N с любыми индексами, кроме 1 или N . Это то, что я пытался сделать в # 20040, и это сильно мешает работе, поскольку исключает возможность индексирования в векторы с завершающим 1 s. Это изменение требует изгиба назад, чтобы гарантировать, что мы определяем индексирование только с 1 или N измерениями. Хотя я полагаю, что теоретически мы могли бы отказаться от индексации с помощью индексов 1 < n < N но сохранить конечные измерения одиночного элемента, указав, что при отправке потребуется поддержка новой подписи: getindex{N}(A::AbstractArray{T,N} where T, I::Vararg{Int, N}, trailing_singletons::Int…) .

Я за первую замену, но против второй.

Re. индексирование с замыкающими синглетонами: хотя индексирование с замыкающими синглетонами иногда удобно при написании кода, этот стиль часто сбивает с толку / запутывает при чтении кода. Код читается чаще, чем пишется, поэтому выгода от запрета индексирования с замыкающими одиночными буквами (ясность), кажется, оправдывает затраты (незначительное снижение удобства).

Отказ от индексирования с любыми другими измерениями, кроме 1 или N получил широкую поддержку в этом потоке. Основным препятствием для завершения этой устаревшей версии, по-видимому, является объем работы, связанной с удалением индексации с замыкающими синглтонами из существующего базового кода. В частности, код линейной алгебры, по-видимому, является основным потребителем индексации с замыкающими одиночками. (Чтение кода линейной алгебры - вот что убедило меня в приведенном выше моменте ясности :).) Если это правильно, и в остальном остается широкая поддержка отказа от индексации с чем-либо, кроме 1 или N размеров, я был бы счастлив, прежде всего, взять на себя необходимую работу, связанную с линейной алгеброй, в следующем цикле выпуска. Лучший!

Я не так уверен в консенсусе, поскольку нить немного извилистая… и я знаю, что я был неточным в своем языке, когда говорил об этих двух вариантах. Было бы здорово услышать обновленные мнения сейчас, когда у нас есть два очень конкретных (и реализованных) варианта. Вы можете попробовать их! CC @andreasnoack , которого здесь явно нет.

Моя нерешительность по поводу индексов только-1-или-N заключается не только в том, что для этого требуется много изменений в существующем коде, но и в том, что его _также_ труднее реализовать и обеспечить. Если бы это было более последовательным чистым упрощением (с обеих сторон), то я думаю, что нашел бы его более привлекательным.

@mbauman Любой из двух вышеперечисленных вариантов будет большим улучшением, но я думаю, что было бы лучше иметь более строгие правила (т.е. исключить индексацию с любыми другими измерениями, кроме 1 или N). Мне непонятно, почему это «труднее реализовать и обеспечить соблюдение». Наивно кажется, что было бы легче, а не сложнее. В любом случае, по моему опыту, это различие становится проблемой только при работе с многомерными массивами (скажем, больше 5 или около того), где отклонение по одному индексу менее визуально различимо, и компилятор действительно помогает в долгом запустить. Я полагаю, что люди, занимающиеся линейной алгеброй, обычно используют массивы меньшей размерности и поэтому не имеют проблем.

Я не могу говорить о сложности реализации, но если мы отдаем приоритет пользовательскому опыту, непреднамеренное предоставление неправильного количества индексов в настоящее время является неприятной ловушкой. Мы получаем жалобы на это в JuMP: https://github.com/JuliaOpt/JuMP.jl/issues/937

Я также за 1 или N. Один из других сотрудников моей лаборатории потратил почти неделю на отслеживание ошибки, вызванной таким поведением.

Подвижная веха теперь, когда # 20079 объединен.

Что здесь остается делать? Вероятно, сейчас самое время полностью отказаться от этого и попытаться упростить всю инфраструктуру индексирования массивов.

Мне нужно потратить некоторое время, чтобы пройти https://github.com/JuliaLang/julia/pull/21750 . Тогда мы можем поговорить о следующих шагах здесь. Я бы поддержал здесь еще один заключительный этап ужесточения проверки границ, чтобы мы разрешили пропущенные конечные измерения только в том случае, если все эти пропущенные размеры равны 1 .

После объединения https://github.com/JuliaLang/julia/pull/21750 , я считаю, что есть только несколько небольших случаев индексирования, которые все еще нужно исключить здесь.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги