Julia: Серьезное отношение к переносу вектора

Созданный на 10 нояб. 2013  ·  417Комментарии  ·  Источник: JuliaLang/julia

из @alanedelman :

Нам действительно следует тщательно подумать о том, как транспонирование вектора должно отправлять различные методы A_*op*_B* . Должно быть возможно избежать новых типов и уродливой математики. Например, вектор Havector, дающий вектор (# 2472, # 2936), вектор, дающий матрицу, и вектор, дающий матрицу (# 2686), - все это плохая математика.

Что для меня работает математически (что позволяет избежать введения нового типа), так это то, что для одномерного Vector v :

  • v' не работает (т.е. просто возвращает v ),
  • v'v или v'*v - скаляр,
  • v*v' - это матрица, а
  • v'A или v'*A (где A - это AbstractMatrix ) - вектор

Обычное _N_-мерное транспонирование меняет порядок индексов на обратный. Вектор, имеющий один индекс, должен быть инвариантным относительно транспонирования.

На практике v' редко используется изолированно и обычно встречается в произведениях матрица-вектор и произведении матрица-матрица. Типичным примером может служить построение билинейных форм v'A*w и квадратичных форм v'A*v которые используются в сопряженных градиентах, факторах Рэлея и т. Д.

Единственная причина для введения нового типа Transpose{Vector} это представление разницы между контравариантными и ковариантными векторами, и я не считаю это достаточно убедительным.

arrays breaking design linear algebra

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

БАМ

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

Например, вектор Havector, дающий вектор (# 2472, # 2936), вектор, дающий матрицу, и вектор, дающий матрицу (# 2686), - все это плохая математика.

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

Как можно v' == v , а v'*v != v*v ? Имеет ли смысл, чем мы думали, чтобы x' * y был отдельным оператором?

Двойное двойственное конечномерное векторное пространство изоморфно ему, а не тождественно.

(Говоря теперь как я) Он не просто изоморфен, он естественно изоморфен, т.е. изоморфизм не зависит от выбора базиса. Я не могу придумать практического приложения, для которого стоило бы различать этот вид изоморфизма и идентичность. ИМО, фактор раздражения возникает из-за такого рода различий.

Имеет ли смысл, чем мы думали, чтобы x' * y был отдельным оператором?

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

Я думаю, что то, о чем спрашивает Джефф, касается денег ... начинает казаться, что x'_y и x_y 'зарабатывают больше
смысл, чем когда-либо.

Я полностью согласен с @stefan. Плохая математика не должна была означать, неправильная математика, это было
означало раздражающую математику. Есть много вещей, которые технически правильны, но не очень хороши ....

Если следовать этой логике, у нас есть два варианта.

x_x остается ошибкой ..... возможно, с предложением "может быть, вы хотите использовать точку"
или x_x - это точечный продукт (мне не нравится этот выбор)

Если x и x' - одно и то же, тогда, если вы хотите, чтобы (x')*y означало dot(x,y) , из чего следует, что x*y также является dot(x,y) . Выхода нет. Мы могли бы сделать x'y и x'*y другой операцией, но я не уверен, что это отличная идея. Люди хотят иметь возможность заключить это в скобки очевидным образом, чтобы это работало.

Я хотел бы отметить, что если мы позволим x*x означать скалярное произведение, пути назад практически не будет. Это будет внедряться в код людей повсюду, и искоренение этого будет кошмаром. Итак, естественный изоморфизм или нет, это не чистая математика, и мы должны иметь дело с тем фактом, что разные вещи в компьютере разные.

Вот практическое обсуждение того, как различать "восходящие кортежи" и "нисходящие кортежи", которые мне нравятся:

http://mitpress.mit.edu/sites/default/files/titles/content/sicm/book-ZH-79.html#% _idx_3310

Он тщательно избегает таких слов, как «вектор» и «двойной», возможно, чтобы не раздражать людей. Я считаю, что применение к частным производным инструментам убедительно:

http://mitpress.mit.edu/sites/default/files/titles/content/sicm/book-ZH-79.html#% _sec_Temp_453

Еще одна причина различать M[1,:] и M[:,1] заключается в том, что в настоящее время наше поведение при широковещании допускает это очень удобное поведение: M./sum(M,1) - стохастический по столбцам, а M./sum(M,2) - стохастический по строкам. . То же самое можно было бы сделать для нормализации, если бы мы «исправили» функцию norm чтобы приложение могло легко работать с строками и столбцами. Конечно, мы все еще могли бы иметь матрицы возврата sum(M,1) и sum(M,2) вместо векторов вверх и вниз, но это кажется немного странным.

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

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

Кроме того, x*y = dot(x,y) сделает * неассоциативным, как в случае x*(y*z) vs. (x*y)*z . Я очень надеюсь, что мы сможем этого избежать.

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

Мы все согласны с тем, что x*x не должно быть скалярным произведением.

Остается вопрос, можем ли мы думать о v'w и v'*w как о скалярном произведении -
Мне очень нравится, как это работает.

@JeffBezanson и я болтали

Предложение следующее:

v' - ошибка векторов (это то, что делает математика)
v'w и v'*w - скалярное произведение (результат = скаляр)
v*w - внешняя матрица продукта (результат = матрица)

Нет различий между строками и векторами-столбцами. Мне все равно понравилось
и был рад увидеть прецедент математики
Из mathematica: http://reference.wolfram.com/mathematica/tutorial/VectorsAndMatrices.html
Благодаря тому, как Mathematica использует списки для представления векторов и матриц, вам никогда не придется различать векторы "строки" и "столбцы".

Пользователи должны знать, что нет векторов-строк .... точка.

Таким образом, если M - матрица

M[1,:]*v - ошибка ..... (при условии, что мы используем M[1,:] - это скаляр
В предупреждении может быть предложено попробовать dot или '* или M[i:i,:]

M[[1],:]*v или M[1:1,:]*v - вектор длины 1 (в любом случае это текущее поведение Джулии)

Относительно тесно связанной проблемы в https://groups.google.com/forum/#!topic/julia -users / L3vPeZ7kews

Mathematica сжимает скалярные секции массива:

m = Array[a, {2, 2, 2}] 


Out[49]= {{{a[1, 1, 1], a[1, 1, 2]}, {a[1, 2, 1], 
   a[1, 2, 2]}}, {{a[2, 1, 1], a[2, 1, 2]}, {a[2, 2, 1], a[2, 2, 2]}}}

In[123]:= Dimensions[m]
Dimensions[m[[All, 1, All]]]
Dimensions[m[[2, 1, All]]]
Dimensions[m[[2, 1 ;; 1, All]]]

Out[123]= {2, 2, 2}

Out[124]= {2, 2}

Out[125]= {2}

Out[126]= {1, 2}

[Изменить: форматирование кода - @StefanKarpinski]

@alanedelman

предполагая, что мы идем с M [1 ,:], является скаляром

вы имеете в виду, что M [1 ,:] - это просто вектор?

Да, прости. Я думал, что M [1 ,:] обрабатывает скаляр 1 :-)

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

Несомненно, у периода много проблем, одна из которых состоит в том, что он просто не
выглядят как "точка" в скалярном произведении, а другой из которых состоит в том, что он конфликтует с
"точечная операция" считывание точки,

Юникод очень хорошо предоставляет символ под названием «оператор точки»
(char(8901)) которые мы можем представить, предлагая

так что (v ⋅ w) стать синонимом (v'*w)

Таким образом, текущее предложение является предметом обсуждения.

  1. Скалярное индексирование убивает измерение, таким образом
    A[i,:] - вектор, как и A[:,i,j]
  2. Векторная индексация толстая
    A[ i:i , : ] или A[ [i], : ] возвращает матрицу с одной строкой
  3. v'w или v'*w - это точечное произведение для векторов (аналогично v*w' для внешнего произведения)
  4. v' не определено для векторов (укажите пользователю permutedims(v,1) ????)
  5. v*A возвращает вектор, если A - матрица
  6. v⋅w также возвращает точечное произведение (но не доходит до . Mathematica, работая с матрицами
  7. v*w не определено для векторов, но предупреждение может подсказать пользователю хорошие предложения, включая

Последствия таковы, что

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

Предложение 5) мне кажется очень странным. Я предпочитаю v'*A чтобы было ясно, что вы используете двойственный вектор. Это особенно важно в сложных векторных пространствах, где двойственность - это не просто преобразование «формы».

Я хочу повторить @StefanKarpinski, что было бы весьма прискорбно потерять во всем этом наше лаконичное поведение при трансляции. Каков краткий синтаксис после этого изменения для взятия вектора v и нормализации столбцов матрицы A на эти значения? В настоящее время можно использовать A ./ v' . Это очень удобно для манипулирования данными.

Хорошие вопросы

Моя схема не запрещает v'*A брать комплексное сопряжение v и умножать на A
и все различные другие случаи, которые я еще не упоминал явно, но легко мог

мы могли исключить 5
возможно это желательно
это не соответствует моему правилу вектора столбца

Такой подход к вещанию милый и тупой.
Одно из решений сейчас - A ./ v[:,[1]]

Он имеет то преимущество, что документирует, какое измерение транслируется на
и обобщает на массивы более высокой размерности

О, и решение v[:,[1]] имеет то достоинство, что НЕ принимает комплексное сопряжение
это, вероятно, то, что намеревается пользователем .....

Мне нравятся эти два примера, потому что первый - это пример ЛИНЕЙНОЙ АЛГЕБРЫ.
где комплексное сопряжение требуется очень часто, но второй пример
МНОГОМЕРНЫЙ ПРИМЕР ДАННЫХ, где мы хотим, чтобы все работало во всех измерениях
не только для матриц, и нам, скорее всего, не нужно комплексное сопряжение

требует # 552. Это уже третий раз за последние две недели.

Еще одна причина различать M [1 ,:] и M [:, 1] заключается в том, что в настоящее время наше поведение широковещательной передачи допускает это очень удобное поведение: M./sum(M,1) является стохастическим по столбцам и M./sum(M, 2) является стохастическим по строкам. То же самое можно было бы сделать для нормализации, если бы мы «исправили» функцию norm, чтобы можно было легко применять ее к строкам и столбцам. Конечно, у нас все еще могут быть матрицы возврата sum (M, 1) и sum (M, 2) вместо векторов вверх и вниз, но это кажется немного неправильным.

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

julia> widen(A::AbstractArray,dim::Int) = reshape(A,insert!([size(A)...],dim,1)...)
# methods for generic function widen
widen(A::AbstractArray{T,N},dim::Int64) at none:1

что позволит использовать код вроде M ./ widen(sum(M,2),2) или A ./ widen(v,1) (см. пример @blakejohnson выше)

M [:, 0 ,:] и v [:, 0] ?????

Я больше с @blakejohnson по вопросу сокращения; Я лично считаю, что размеры squeeze понятнее, чем widen . Я подозреваю, что я бы постоянно смотрел документы, чтобы выяснить, вставляет ли widen измерение в указанный индекс или после него, и нумерация становится немного более сложной, если вы хотите расширить сразу несколько измерений. (Что делает widen(v, (1, 2)) для вектора v ?) Все это не является проблемой для squeeze .

Независимо от того, будем ли мы расширять или сжимать по умолчанию, я думаю, что Джулия должна последовать примеру numpy, когда дело доходит до расширения, и разрешить что-то вроде v[:, newaxis] . Но я действительно считаю, что предпочитаю сохранять размеры, а не отбрасывать их, сложнее отловить ошибку, когда вы случайно расширили неправильную сторону, чем когда вы сжали неправильную сторону (что обычно приводит к ошибке).

В списке @alanedelman
я чувствую что

v * A возвращает вектор, если A - матрица

не хорошо.

v_A должно быть ошибкой, если A не 1x1 (несоответствие диапазона индекса)
v'_A должен быть правильным способом сделать это.

Один из способов решения этой проблемы - автоматическое преобразование вектора v в матрицу nx1 (при необходимости).
и всегда обрабатывать v 'как матрицу 1xn (никогда не преобразовывать ее в вектор или матрицу nx1)
Также мы позволяем автоматически преобразовывать матрицу 1x1 в масштабирующее число (при необходимости).

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

Единый способ справиться со всеми этими проблемами - разрешить автоматическое (тип?) Преобразование (при необходимости)
между массивами размера (n), (n, 1), (n, 1,1), (n, 1,1,1) и т.д. (но не между массивами размера (n, 1) и (1, n) )
(Точно так же, как мы автоматически конвертируем действительное число в комплексное, когда это необходимо)

В этом случае массив размера (1,1) может быть преобразован в число (при необходимости) (см. # 4797)

Сяо-Ган (физик)

Однако остается v'_A .... Я действительно хочу, чтобы v'_A * w работал

Мое впечатление от линейной алгебры в Julia таково, что она очень похожа на матричную алгебру, хотя существуют скаляры и истинные векторы (что, я думаю, хорошо!)

Давайте рассмотрим, как обращаться с таким продуктом, как x*y*z*w , где каждый фактор может быть скаляром, вектором или матрицей, возможно, с транспонированием. Матричная алгебра определяет произведение матриц, где матрица имеет размер n x m . Один из подходов - расширить это определение так, чтобы n или m можно было заменить на absent , что при вычислении продукта будет действовать как значение единицы. , но используется для различения скаляров и векторов от матриц:

  • скаляр будет absent x absent
  • вектор (столбец) будет n x absent
  • вектор-строка будет absent x n

В идеале мы хотели бы организовать все так, чтобы нам никогда не приходилось представлять векторы строк, но этого было бы достаточно для реализации таких операций, как x'*y и x*y' . У меня такое ощущение, что многие из нас ищут именно такую ​​схему.

Но я начинаю подозревать, что запрет векторов-строк в такой схеме дорого обойдется. Пример: подумайте, как нам нужно заключить продукт в скобки, чтобы избежать формирования вектора-строки на любом промежуточном этапе: ( a - скаляр, u и v - векторы)

a*u'*v = a*(u'*v) // a*u' is forbidden
v*u'*a = (v*u')*a // u'*a is forbidden

Чтобы оценить продукт x*y'*z , избегая при этом создания векторов-строк, нам необходимо знать типы факторов, прежде чем выбирать порядок умножения! Если пользователь сделает это сам, это будет препятствием для общего программирования. И я не уверен, как Джулия могла делать это автоматически и разумным образом.

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

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

Транспонирование - это просто особый способ перестановки режимов. Если вы разрешите v.' где v - вектор, то permutedims(v,[2 1]) должен вернуть то же самое. Либо оба возвращают специальный тип вектора-строки, либо вводят новое измерение.

Наличие специального типа для векторов-строк не кажется мне хорошим решением, потому что что вы будете делать с другими типами векторов режима n, например, permutedims([1:4],[3 2 1]) ? Я призываю вас также принять во внимание полилинейную алгебру, прежде чем принимать решение.

@toivoh упомянул, что

"Один из подходов заключался бы в расширении этого определения так, чтобы n или m можно было заменить отсутствующим, что действовало бы как значение единицы при вычислении продукта, но используется для различения скаляра и векторов от матриц:

  1. скаляр отсутствовал бы x отсутствовал
  2. вектор (столбец) будет отсутствовать nx
  3. вектор-строка отсутствовала бы xn "

В многолинейной алгебре (или для тензоров высокого ранда) приведенное выше предложение соответствует использованию absent для представления
многие индексы диапазона 1, то есть размер (m, n, отсутствует) может соответствовать (m, n), (m, n, 1), (m, n, 1,1) и т. д.

Если мы воспользуемся этой интерпретацией «отсутствует», то 1. и 2. - это нормально и приятно иметь, но 3. может и не подойти.
Мы не хотим смешивать массивы размера (1, n) и (1,1, n).

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

[TL; DR: перейти к РЕЗЮМЕ]

Вот наиболее распространенные сценарии, в которых я обнаружил потребность в большей универсальности обработки массивов, чем обычные операции матрица-вектор:

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

(2) Контроль оценки: например, для любого продукта, который можно вычислить, нужно иметь возможность вычислять любую под-сущность этого продукта по отдельности, потому что кто-то может пожелать объединить его с несколькими различными под-сущностями для образования разных продуктов. Таким образом, озабоченность Тойво по поводу, например, запрета a*u' - это не просто проблема компиляции, а проблема программирования; еще более распространенным вариантом является предварительное вычисление x'Q для вычисления квадратичных форм x'Q*y1 , x'Q*y2 , ... (где это должно выполняться последовательно).

(3) Упрощение кода: несколько раз, имея дело с арифметическими операциями, отображенными на многомерные наборы данных, я обнаружил, что 6-7 строк непостижимого цикла или кода отображения функций можно заменить одной или двумя короткими операциями с массивами в системах. которые обеспечивают соответствующую общность. Намного читабельнее и быстрее.

Вот мой общий опыт работы с вышеуказанными системами:

MATLAB: базовый язык ограничен обычными матрично-векторными операциями, поэтому обычно заканчиваются написанием циклов с индексацией.

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

Mathematica: Чистый и очень общий - в частности, все соответствующие операторы разработаны с учетом поведения тензоров более высокого порядка. Помимо точки, см., Например, документы по транспонированию, выравниванию / разделению и внутреннему / внешнему. Комбинируя только эти операции, вы уже можете охватить большинство вариантов использования манипуляций с массивами, а в версии 9 они даже добавили дополнительные операции тензорной алгебры к основному языку. Обратной стороной является то, что даже несмотря на то, что метод Mathematica что-то делает чистым и имеет смысл (если вы знаете язык), он может явно не соответствовать обычным математическим обозначениям для этого. И, конечно же, из-за общности сложно понять, как будет работать код.

scmutils: для функционального анализа он чистый, общий и обеспечивает наиболее математически интуитивно понятные операции (как запись, так и чтение) из всех вышеперечисленных. Идея кортежа вверх / вниз на самом деле является просто более последовательным и более общим расширением того, что люди часто делают в письменной математике, используя знаки транспонирования, соглашения о дифференциации и другие полустандартизированные понятия; но все просто работает. (Чтобы написать докторскую диссертацию, я разработал последовательную и недвусмысленную нотацию, напоминающую традиционную математическую нотацию, но изоморфную синтаксису Sussman & Wisdom SICM.) Они также использовали ее для реализации дифференциальной геометрии [1], которая имеет вдохновил на перенос на SymPy [2]. Я не использовал его для анализа данных, но ожидаю, что в контексте общего массива, где вам нужен только один вид кортежа (например, список Mathematica), вы можете просто выбрать один («вверх») по соглашению. Опять же, универсальность скрывает от программиста соображения производительности, но я надеюсь, что это та область, где Джулия может преуспеть.

РЕЗЮМЕ

Я думаю, что предлагаемый тип транспонированного вектора следует охарактеризовать как более общий кортеж «вниз» в scmutils, тогда как обычные векторы будут кортежами «вверх». Назвать их чем-то вроде «вектор» и «транспонированный вектор», вероятно, будет иметь больше смысла для людей, чем называть их «вверх» и «вниз» (за счет краткости). Это поддержит три категории использования:

(1) для анализа данных, если людям просто нужны вложенные массивы, им нужен только «вектор»;
(2) для базовой матрично-векторной линейной алгебры люди могут использовать «вектор» и «транспонированный вектор» в прямом соответствии с математическим соглашением («матрица» будет эквивалентна «транспонированному вектору» из «вектора»);
(3) для тензорных операций более высокого порядка (где меньше стандартизации и людям обычно все равно приходится думать) реализация должна поддерживать полную универсальность системы арифметики кортежей двух видов.

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

[1] http://dspace.mit.edu/handle/1721.1/30520
[2] http://krastanov.wordpress.com/diff-geometry-in-python/

@thomasmcoffee звучит так, будто вы выступаете за явное различие между ко- и контравариантными векторами.

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

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

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

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

immutable UpDownTensor{T, N, UPMASK} <: AbstractArray{T, N}
    A::AbstractArray{T, N}
end

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

Некоторые случайные мысли:

  • Будет ли квадратичная / билинейная форма лучше представлена ​​двумя нижними измерениями?
  • Если бы транспонирование соответствовало бы просто переворачиванию вверх / вниз каждого измерения, я предполагаю, что мы также получили бы тип транспонированной матрицы с первым измерением вниз и вторым вверх.
  • Шаблоны повышения, соответствующие значениям по умолчанию, могут быть представлены непосредственно базовым массивом, а не обертывать его.

Что ж, это, безусловно, одно из обобщений типа Transposed , и оно определенно имеет некоторые достоинства. Не уверен, возможно ли это.

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

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

Возвращаясь к моему более раннему примеру, предположим, что вы вычисляете ряд квадратичных форм x'Q*y1, x'Q*y2, ... для разных векторов y1, y2, ... . Следуя SICM, обозначьте восходящие кортежи (векторы-столбцы) (...) и нисходящие кортежи (векторы-строки) [...] . Если вы хотите сделать все это сразу, и вы застряли только на верхних / нижних позициях, обычным способом было бы объединить yi в матрицу Y = [y1, y2, ...] используя нижний кортеж (из кортежи вверх) и вычислить r = x'Q*Y , что даст вам результаты в виде кортежа вниз r . Но что, если вы хотите умножить каждый из этих результатов на вектор (столбец) v ? Вы не можете просто сделать r*v , потому что вы получите сокращение (скалярное произведение). Вы можете преобразовать r в восходящий кортеж, а затем умножить, что даст вам результаты в восходящем кортеже (из восходящих кортежей). Но предположим, что для следующего шага вам понадобится кортеж вниз? Семантически у вас есть измерение, проходящее через ваши вычисления, которое просто представляет набор вещей, которые вы всегда хотите транслировать; но для достижения этого в мире строго вверх / вниз вы должны продолжать выполнять произвольные контекстно-зависимые преобразования, чтобы вызвать правильное поведение.

Напротив, предположим, что у вас также есть нейтральные кортежи (массивы), обозначенные {...} . Затем вы, естественно, пишете ys = {y1, y2, ...} как массив (восходящих кортежей), так что r = x'Q*ys - это массив, а r*v также является массивом (восходящих кортежей). Все имеет смысл, и никаких произвольных преобразований не требуется.

Стефан предполагает, что отличать одномерные массивы от векторов вверх / вниз катастрофически, но я думаю, что эта проблема решается тем фактом, что большинство функций имеет смысл работать с векторами _или_ на массивах, но НЕ _ ни то, ни другое. (Или на матрицах _или_ на массивах векторов _или_ на векторах массивов _или_ на массивах массивов, но НЕ _ ни то, ни другое. И так далее. Таким образом, при соответствующих правилах преобразования я не придумал общий случай, который бы не подошел правильная вещь автоматически. Может кто сможет?

Посмотрев глубже [1], я обнаружил, что scmutils фактически отличает то, что они называют «векторами», от кортежей вверх и вниз под капотом; но в настоящее время правила преобразования настроены так, что эти «векторы» отображаются на кортежи вверх (как я предлагал ранее) всякий раз, когда они входят в мир вверх / вниз, с оговоркой, что «Мы оставляем за собой право изменять эту реализацию, чтобы различать Схема векторов от кортежей вверх ". (Возможно, кто-то в университетском городке может спросить GJS, имел ли он в виду какие-либо конкретные идеи.) Система Sage [2] в значительной степени отделяет обработку массивов от векторов и матриц (в настоящее время нет основной поддержки тензоров), и единственные проблемы, с которыми я столкнулся это связано с отсутствием встроенного преобразования между ними в случаях, которые, очевидно, имеют смысл.

[1] http://groups.csail.mit.edu/mac/users/gjs/6946/refman.txt --- начиная с «Структурированные объекты»
[2] http://www.sagemath.org/

Я разговаривал с @jiahao за обеденным столом, и он упомянул, что команда Джулии пыталась выяснить, как обобщить операции линейной алгебры на массивы более высокой размерности. Два года назад я несколько месяцев думал об этом, потому что мне это было нужно для KroneckerBio. Я хотел поделиться своим подходом.

Давайте пока рассмотрим только произведение между двумя массивами. Аналогичное обобщение имеют и другие операции. При работе с массивами три наиболее распространенных типа продуктов - это внешний продукт, внутренний продукт и поэлементный продукт. Обычно мы думаем о выполнении подобных операций между двумя объектами, такими как inner(A,B) или A*B . Однако при выполнении этих операций с многомерными массивами они выполняются не между массивами в целом, а между отдельными измерениями массивов. Множественные внешние / внутренние / поэлементные подоперации происходят в одной операции между двумя массивами, и каждое измерение каждого массива должно быть назначено ровно одной подоперации (либо явно, либо по умолчанию). Для внутренних и поэлементных продуктов один размер слева должен быть соединен с размером справа одинакового размера. Внешние размеры продукта не должны быть сопряжены. Большую часть времени пользователь создает либо внутренний продукт, либо поэлементный продукт между парой измерений и внешним продуктом для всех остальных. Внешний продукт подходит по умолчанию, потому что он наиболее распространен и не требует сопряжения.

Я обычно считаю размеры именованными, а не упорядоченными, как оси x, y и z графика. Но если вы хотите, чтобы пользователи действительно могли получить доступ к массивам с помощью упорядоченной индексации (например, A[1,2,5] а не A[a1=1, a3=5, a2=2] ), тогда у вас должна быть согласованная процедура для упорядочивания результатов операции. Я предлагаю упорядочить результат, перечислив все измерения первого массива с последующим перечислением всех измерений второго массива. Все измерения, которые участвовали во внутреннем продукте, вытесняются, а для измерений, которые участвовали в поэлементном продукте, выжимается только измерение из второго массива.

Я сделаю для этого несколько обозначений. Не стесняйтесь Юлиафи это. Пусть A будет массивом, который равен a1 by a2 by a3 и пусть B будет массивом, который равен b1 от b2 . Предположим, что array_product(A, B, inner=[2, 1], elementwise=[3, 2]) примет внутренний продукт между измерениями a2 и b1 , поэлементный продукт между a3 и b2 , а внешний продукт a1 . Результатом будет массив размером a1 на a3 .

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

A*B Matlab - это array_product(A, B, inner=[2,1]) .

A.' Matlab - это permute(A, B, [2,1]) где permute сохраняет неизменными все измерения, превышающие количество третьего аргумента.

Вы можете выбрать, выдавать или нет ошибки, если размерность массивов больше 2 или даже не равна 2, как это делает Mathematica с векторным транспонированием. Если вы используете только общие вычисления массива, вам не нужно решать, принимать ли предложение @wenxgwen интерпретировать все (n, m) массивы как (n, m, 1) и (n, m, 1). , 1). Только при использовании операторов линейной алгебры или других операторов, которые ожидают массив или определенную размерность, вы должны принять это решение. Мне нравится предложение @wenxgwen , потому что у динамически типизированного языка есть небольшие недостатки.

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

Спасибо за перспективу! Я нашел это весьма поучительным, чтобы понять, что это за зверь, который на самом деле представляет собой обычный массив * массив.

Может быть интересно сопоставить предложения по умножению многомерных массивов с семантикой, предложенной для оператора умножения матриц в PEP 0465 . В частности:

Входные данные вектора 1d повышаются до 2d путем добавления или добавления «1» к форме, операция выполняется, а затем добавленное измерение удаляется из выходных данных. 1 всегда добавляется «снаружи» фигуры: перед левыми аргументами и добавляется для правых аргументов. В результате оба типа matrix @ vector и vector @ matrix являются допустимыми (при условии совместимости форм), и оба возвращают 1d-векторы; vector @ vector возвращает скаляр ... Неудача этого определения для 1d векторов состоит в том, что в некоторых случаях оно делает @ неассоциативным ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2)). Но это, похоже, тот случай, когда практичность важнее чистоты.

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

С PEP 0465:

Несовершенство этого определения для 1d векторов состоит в том, что в некоторых случаях оно делает @ неассоциативным ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2))

Примечательно, что такое определение может давать неверные результаты в Mathematica, потому что Dot ( . ) предполагается ассоциативным ( Flat ) при символической оценке (как в случае с f ниже, но не с g ):

In[1]:= f=X.(y.Z);
g:=X.(y.Z)

In[3]:= Block[{
X=Array[a,{2,2}],
y=Array[b,2],
Z=Array[c,{2,2}]
},{f,g}]

Out[3]= {{(a[1,1] b[1]+a[1,2] b[2]) c[1,1]+(a[2,1] b[1]+a[2,2] b[2]) c[2,1],(a[1,1] b[1]+a[1,2] b[2]) c[1,2]+(a[2,1] b[1]+a[2,2] b[2]) c[2,2]},{a[1,1] (b[1] c[1,1]+b[2] c[2,1])+a[1,2] (b[1] c[1,2]+b[2] c[2,2]),a[2,1] (b[1] c[1,1]+b[2] c[2,1])+a[2,2] (b[1] c[1,2]+b[2] c[2,2])}}

In[4]:= SameQ@@Expand[%]
Out[4]= False

Из @drhagen :

Julia с проверкой типов во время выполнения и методами convert не страдает от этой двусмысленности.

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

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

Для работы в математической области линейной алгебры должно быть еще три типа: Matrix , ColumnVector и RowVector , на которых все обычные операторы и функции линейной алгебры работать как обычно.

Теперь, когда структура типа определена правильно, вы можете упростить задачу для пользователя, добавив неявное преобразование для Matrix в Array{2} , ColumnVector в Array{1} , и от RowVector до Array{2} (не уверен насчет этого), от Array{2} до Matrix и от Array{1} до ColumnVector .

Мое предложение выше (https://github.com/JuliaLang/julia/issues/4774#issuecomment-32705055) позволяет каждому измерению многомерной структуры различать, имеет ли она нейтральный ("коллекция" / "массив"), вверх (" столбец ") или вниз (" строка ") семантика. Думаю, то, что вы описываете, - это особый случай.

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

@thomasmcoffee Мне очень нравится ваше предложение. Я реализовал что-то отдаленно похожее в DSL (давно, далеко) с несколькими руководящими принципами (также известными как личное мнение):

  1. Понятие двойственности как отличия жизненно важно для любой разумно самосогласованной семантики.
  2. Специальное применение тензорной алгебры с параметризованными операторами (или чем-то внешним по отношению к данным в этом отношении) эстетически сильно неприятно.

Самые большие жалобы, которые я тогда получил (и они были громкими), касались именно тех неудобств, которые решает ваша трехвалентная семантика (добавление понятия нейтральной коллекции). Ницца! Эта идея никогда не приходила мне в голову, но теперь, когда вы изложили ее, она имеет такой смысл. Очень хотелось бы использовать такую ​​систему, причем для реальной работы. Было бы хорошо, если бы Юля смогла с этим справиться!

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

Думаю, ты прав, Дэвид. На самом деле мы говорим о двух вариантах использования.

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

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

Возможно, тензоры и TDT входят в пакет, а не в ядро, а только на
основания относительной популярности. Но, как и заявление Джулии о
Независимость говорит, что Джулия рождена от жадности. И, как говорит Гордон Гекко,
жадность это хорошо. :)
21 марта 2014 г. в 3:14 «Дэвид Хаген» [email protected] написал:

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

Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -38262998
.

Я думаю, что бесшовная интеграция определенно достижима при наличии достаточно богатого семейства шрифтов. Расширение https://github.com/JuliaLang/julia/issues/4774#issuecomment -32693110 Тойво, описанное выше, может начаться примерно так:

immutable AbstractTensorArray{T, N, UPMASK, DOWNMASK} <: AbstractArray{T, N}
    A::AbstractArray{T, N}
end
# where !any(UPMASK & DOWNMASK)

typealias AbstractColumnVector{T} AbstractTensorArray{T, 1, [true], [false]}
typealias AbstractRowVector{T} AbstractTensorArray{T, 1, [false], [true]}
typealias AbstractMatrix{T} AbstractTensorArray{T, 2, [false, true], [true, false]}

(в настоящее время AbstractMatrix{T} просто псевдонимы AbstractArray{T, 2} ; потенциально здесь можно использовать другое имя)

Отсюда логичными выглядят следующие реализации:

  1. Обобщенный метод transpose после перестановки измерений и соответствующих индексов UPMASK и DOWNMASK меняет местами UPMASK и DOWNMASK. Нейтральные размеры не пострадают.
  2. Любые подтипы AbstractArray{T, N} обычно по умолчанию преобразуются в соответствующие чередующиеся подтипы AbstractTensorArray{T, N, [..., false, true, false, true], [..., true, false, true, false]} в тензорных операциях. Это сохраняет существующую семантику специального синтаксиса массивов Джулии для векторов и матриц.
  3. Метод конструктора (скажем, array ) для AbstractTensorArray используется для создания нейтральных размеров и может комбинировать другие AbstractTensorArray s (или типы, конвертируемые в них) для создания объединенного AbstractTensorArray с нейтральным измерением верхнего уровня.

Рассматривая примеры @drhagen :

умножение векторных матриц, умножение скалярных массивов

c = 1               # Int
v = [1, 2]          # Array{Int, 1}
M = [[1, 2] [3, 4]] # Array{Int, 2}

# scalar-array
c * M               # UNCHANGED: *(Int, Array{Int, 2}) => Array{Int, 2}

# matrix-vector
M * v               # *(Array{Int, 2}, Array{Int, 1}) => *(Matrix{Int}, ColumnVector{Int}) => ColumnVector{Int}

# vector-matrix
v' * M              # transpose(Array{Int, 1}) => transpose(ColumnVector{Int}) => RowVector{Int}
                    # *(RowVector{Int}, Array{Int, 2}) => *(RowVector{Int}, Matrix{Int}) => RowVector{Int}

# (1-array)-(2-array)
v .* M              # UNCHANGED: .*(Array{Int, 1}, Array{Int, 2}) => Array{Int, 2}

(с использованием Matrix с определением, соответствующим определению AbstractMatrix выше)

распределение добавления одного массива по массиву массивов

Я считаю, что семантически это означает добавление одного вектора к массиву векторов, добавление одной матрицы к массиву матриц и так далее:

# vector-(vector-array)
ws = array([1, 2], [3, 4])
                    # TensorArray{Int, 2, [false, true], [false, false]}
v + ws              # +(Array{Int, 1}, TensorArray{Int, 2, [false, true], [false, false]}) => +(ColumnVector{Int}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 2, [false, true], [false, false]}
# => array([2, 4], [4, 6])

# array-(vector-array)
u = array(1, 2)     # TensorArray{Int, 1, [false], [false]}
u + ws              # +(TensorArray{Int, 1, [false], [false]}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 2, [false, true], [false, false]}
# => array([2, 3], [5, 6])
# alternatively:
v .+ ws             # .+(Array{Int, 1}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 2, [false, true], [false, false]}
# => array([2, 3], [5, 6])
# same effect, but meaning less clear:
v .+ M              # UNCHANGED: .+(Array{Int, 1}, Array{Int, 2}) => Array{Int, 2}
# => [[2, 4] [4, 6]]

# matrix-(matrix-array)
Ns = array([[1, 2] [3, 4]], [[5, 6] [7, 8]])
                    # TensorArray{Int, 2, [false, false, true], [false, true, false]}
M + Ns              # +(Array{Int, 2}, TensorArray{Int, 2, [false, false, true], [false, true, false]}) => +(Matrix{Int}, TensorArray{Int, 2, [false, false, true], [false, true, false]}) => TensorArray{Int, 2, [false, false, true], [false, true, false]}
# => array([[2, 4] [6, 8]], [[6, 8] [10, 12]])

Рассматривая мой предыдущий пример масштабирования вектора v помощью нескольких различных квадратичных форм x'M*w1, x'M*w2, ... , для окончательного результата x'M*w1*v, x'M*w2*v, ... :

x = v
x' * M * ws * v     # *(RowVector{Int}, Array{Int, 2}) => *(RowVector{Int}, Matrix{Int}) => RowVector{Int}
                    # *(RowVector{Int}, TensorArray{Int, 2, [false, true], [false, false]}) => TensorArray{Int, 1, [false], [false]}
                    # *(TensorArray{Int, 1, [false], [false]}, Array{Int, 1}) => *(TensorArray{Int, 1, [false], [false]}, ColumnVector{Int}) => TensorArray{Int, 1, [false, true], [false, false]}
# => array([27, 54], [59, 118])

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

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

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

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

Минималистично это выглядит примерно так:

immutable Space
    dim::Int
    dual::Bool
end
Space(dim::Int)=Space(dim,false) # assume normal vector space by default
dual(s::Space)=Space(s.dim,!s.dual)

matrix=Tensor((Space(3),dual(Space(5))))
# size is no longer sufficient to characterise the tensor and needs to be replaced by space
space(matrix) # returns (Space(3),dual(Space(5))) 
space(matrix') # returns (Space(5),dual(Space(3)))

Конечно, вы можете изобрести какой-нибудь синтаксис, чтобы не писать пробел постоянно. Вы можете создать другой тип пространства, для которого dual (s) == s, чтобы иметь тензоры, которые не различают индексы вверх и вниз, и так далее. Но, конечно, это невозможно встроить в стандартный тип Array Джулии, не нарушив всего ...

Мне всегда было интересно, почему нет более тесной взаимосвязи между тем, как тензоры используются в инженерии / физике, и тем, как они обрабатываются в математических программах. Я нашел интересную беседу об обмене стеками на эту тему ... http://math.stackexchange.com/questions/412423/differences-between-a-matrix-and-a-tensor. Здесь также был хороший справочный пост.
http://www.mat.univie.ac.at/~neum/physfaq/topics/tensors

Я часто использую Matlab для повседневных научных вычислений, но новичок в Julia. Здесь я заметил, что ведется много дискуссий о многомерном умножении и транспонировании массивов или других подобных операциях с массивами. Предлагаю взглянуть на http://www.mathworks.com/matlabcentral/fileexchange/8773-multiple-matrix-multiplications--with-array-expansion-enabled

Он в основном следует синтаксису, аналогичному тому, что @drhagen упомянул в более ранней публикации, например array_product (A, B, inner_A_dim = [1, 2], inner_B_dim = [3, 4]) для продукта между массивами A и B на заданные внутренние размеры.

Это один из пакетов Matlab, который может применять операции умножения или транспонирования к некоторым выбранным измерениям. В пакете есть руководство о том, как реализовать эти операции в Matlab, но я думаю, что математическая теория должна применяться и для других языков. Их идея состоит в том, чтобы реализовать операции с массивами, избегая использования циклов For и в основном полагаясь на изменение формы массива и т. Д. Итак, в Matlab это очень быстро. Я не знаю, нравится ли Джулии векторизованные операции или депекторизованные операции (кажется, более поздняя). Я считаю, что векторизованные операции являются преимуществом для операций с многомерными массивами, если ядро ​​поддерживает их. Возможно, на данном этапе нам стоит серьезно подумать о таких операциях с массивами.

Для справки: еще одна аналогичная реализация в Matlab для операции INV находится здесь: http://www.mathworks.com/matlabcentral/fileexchange/31222-inversion-every-2d-slice-for-arbitrary-multi-dimension-array

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

еще одно голосование здесь за предложенное @alanedelman решение наверху. вот мотивирующий пример.

прямо сейчас срез строки - это 2d массив, а срез столбца - 1d массив; что странно асимметрично и некрасиво:

julia> A = randn(4,4)
4x4 Array{Float64,2}:
  2.12422    0.317163   1.32883    0.967186
 -1.0433     1.44236   -0.822905  -0.130768
 -0.382788  -1.16978   -0.19184   -1.15773
 -1.2865     1.21368   -0.747717  -0.66303

julia> x = A[:,1]
4-element Array{Float64,1}:
  2.12422
 -1.0433
 -0.382788
 -1.2865

julia> y = A[1,:]
1x4 Array{Float64,2}:
 2.12422  0.317163  1.32883  0.967186

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

julia> dot(y[:],x)
2.4284575954571106
julia> (y*x)[1]
2.42845759545711

Это не связное предложение, если вы не сделаете '* специальным оператором, что довольно сомнительно, поскольку тогда x'*y и (x')*y не означают одно и то же. Более того, это сделало бы умножение неассоциативным.

я понимаю трудности с x_y 'и y'_x. Может лучше лечить
внутренние и внешние продукты как отдельные операции, например, с использованием точки (). (Возможно
также используя cdot?)

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

В среду, 16 июля 2014 г., в 20:17, Стефан Карпинский [email protected]
написал:

Это не связное предложение, если вы не сделаете '* специальный оператор, который
довольно сомнительно, поскольку тогда x'_y и (x ') _ y не означают одно и то же.
Более того, это сделало бы умножение неассоциативным.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -49254346.

Мадлен Уделл
Кандидат вычислительной и математической инженерии
Стэндфордский Университет
www.stanford.edu/~udell

@madeleineudell , я согласен с вами, но это другой вопрос, см. №5949. Хотя этот вопрос кажется закрытым, я не помню, чтобы было четкое соглашение или заключение.

Как только мы переключимся на представления массива, станет легче исследовать эти направления. В частности, сказав slice(A, i, :) вы получите желаемое поведение. (Он делает это прямо сейчас, но за счет введения более медленного типа SubArray.)

С чисто математической точки зрения все проблемы, представленные здесь, возникают из-за смешения (и путаницы) того, что мы подразумеваем под массивами и того, что мы подразумеваем под векторами / тензорами / матрицами. Концептуально массивы - это просто списки (или, в случае массивов n-dim, списки списков). Таким образом, нет естественной спецификации для таких операций, как умножение массивов, транспонирование и т. Д. Хотя такие функции, как permutedims, поэлементные операции и операции, специфичные для оси (среднее, медиана и т. Д.), Имеют смысл и могут быть однозначно определены в Естественно, таких операций как скалярное произведение быть не может.

Как упоминалось выше, векторы и тензоры являются геометрическими объектами, и хотя их можно представить с помощью массивов, эти представления не содержат такого же богатства структуры, как математические объекты, которые они представляют. Транспонирование одномерного массива не выполняется; транспонирование вектора является его двойственным. Транспонирование 2-мерного массива может быть однозначно и естественно определено как перестановка его измерений, но это обычно неверно для тензоров: в то время как естественный случай имеет место для тензоров ранга (1,1) (также известных как матрицы), a тензор ранга (2,0) переходит в тензор ранга (0,2). Опять же, если рассматривать тензоры как массивы, геометрическая информация, которая делает тензоры тензорами, теряется.

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

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

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

Я считаю, что общее семейство AbstractArray, (многомерный) контейнер данных, является достаточно общим, чтобы быть незаменимой частью любого технического языка программирования. Тензор, подчиняющийся строгим математическим правилам, хотя я очень забочусь о нем, - хороший объект для специального пакета. Фактически, я работаю над чем-то в соответствии с направлениями, указанными @jdbates в https://github.com/Jutho/TensorToolbox.jl . Это пока недокументировано и в основном не протестировано. Я написал его для вещей, которые мне нужны лично в квантовой физике многих тел, но я надеюсь, что он построен таким образом, чтобы он был достаточно общим и расширяемым, чтобы быть полезным для большего сообщества математиков и физиков, которым небезразлична работа с тензорами.

Чтобы дать некоторые подробности (скопировано с форума JuliaQuantum): я решил определить новую иерархию типов для тензоров, которая не зависит от типа AbstractArray Джулии (хотя основной Tensor - это просто оболочка для Array). Предполагается, что эта иерархия типов работает немного более формально. Тензорные индексы связаны с векторными пространствами (далее называемыми индексными пространствами), и если тип векторного пространства, с которым связан тензорный индекс, отличается от его двойственного, это соответствует тензору, который различает ковариантные и контравариантные индексы.

Итак, первая часть пакета - это абстрактная часть для определения векторных пространств, где я сопоставляю иерархию типов объектов Julia с математической иерархией векторных пространств. Общее векторное пространство V бывает четырех разновидностей, соответствующих теории представлений общей линейной группы на V, то есть самого V (фундаментальное представление), конъюнктуры (V), дуального (V) и дуального (конъюнкта (V)). Для вещественных векторных пространств cons (V) = V и существует только V и дуальное (V), соответствующие контравариантным и ковариантным векторам. Затем есть внутренние пространства продукта, а на верхнем уровне иерархии находятся евклидовы пространства, которые представляют собой пространства внутреннего продукта со стандартным евклидовым внутренним продуктом (то есть ортогональным базисом). В физике также полезно думать о векторных пространствах, которые разложены на разные секторы, то есть они градуированы, например, с помощью неприводимых представлений действий симметрии.

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

Затем должны быть определенные операции, определенные с тензорами, наиболее важными из которых являются сжимающие тензоры, а также, например, ортогональные факторизации (разложение по сингулярным значениям) и т. Д. И, наконец, должны быть линейные отображения, отображающие один тензор на другой. Они заслуживают особого типа, поскольку обычно не нужно полностью кодировать их как матрицу, а скорее так, чтобы можно было эффективно вычислить произведение векторов матрицы для использования в итерационных методах (Ланцоша и т. Д.). Два моих существующих пакета (TensorOperations.jl и LinearMaps.jl) реализуют эту функциональность для стандартных массивов, создаваемый тензорный набор инструментов будет перегружать / переопределять их для новой иерархии AbstractTensor.

Я надеюсь, что этот пакет является достаточно общим, так что он также будет полезен для более широкого сообщества физиков и математиков. Например, если появится кто-то, который создаст пакет для работы с многообразиями, он может затем определить векторное пространство TangentSpace как подпространство абстрактного InnerProductSpace, а затем сразу же создать тензоры, живущие в тензорном произведении нескольких касательных и котангенсных пространств. Фактически, я думаю о разделении части для определения векторных пространств в отдельный пакет, который мог бы вырасти в пакет для определения математических структур / объектов.

Наконец, взаимодействие со стандартной julia происходит из вызова tensor на стандартном Array , который превращает его в объект типа Tensor с индексами, связанными с пробелами типа CartesianSpace . Это стандартное вещественное векторное пространство R ^ n с евклидовым произведением, в котором нет различия между ковариантным и контравариантным индексом. Я думаю, это лучше всего влечет за собой то, что представляет собой стандартный массив Julia.

@JeffBezanson , я неоднозначно отношусь к обработке массивов как подмножеств тензоров. При этом информация не теряется, но в то же время существует несколько возможных интерпретаций массивов, а интерпретация тензора не всегда (или даже обычно) имеет смысл. Рассмотрим изображения: изображение можно рассматривать как векторное поле на (обычно 2d) многообразии. Ограничение этого поля прямоугольной сеткой дает вам структуру, которую, естественно, вы хотите представить с помощью трехмерного массива. Однако на самом деле это просто отображение пространства точек сетки в векторное пространство {R, G, B}, поэтому геометрический смысл первых двух измерений (метки x и y сетки) отличается от геометрический смысл третьего измерения (которое, по сути, является вектором).

Я не против предложения @Jutho о разделении тензорной механики в отдельный пакет. Вероятно, он прав в том, что количество пользователей, которым нужна полная тензорная механика, намного меньше, чем количество людей, которым просто нужны простые операции с массивами. Вопрос, который мы действительно пытаемся здесь задать, состоит в том, «в какую область должна попадать линейная алгебра?»

Аппарат линейной алгебры - это достаточно существенное подмножество аппарата тензорной алгебры, так что, по крайней мере, на мой взгляд, нет смысла реализовывать первый без реализации второго. Такие операции, как v'M, будут представлены более кратко и последовательно, если у нас есть понятие ко- и контравариантных векторов, но это уже подводит нас к большей части пути к общим тензорным операциям.

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

Рассмотрим изображения: изображение можно рассматривать как векторное поле на (обычно 2d) многообразии. Ограничение этого поля прямоугольной сеткой дает вам структуру, которую, естественно, вы хотите представить с помощью трехмерного массива. Однако на самом деле это просто отображение пространства точек сетки в векторное пространство {R, G, B}, поэтому геометрический смысл первых двух измерений (метки x и y сетки) отличается от геометрический смысл третьего измерения (которое, по сути, является вектором).

Хотя это не затрагивает и не убирает ваше общее сообщение, https://github.com/timholy/Images.jl/pull/135 работает над реализацией этой идеи для изображений. Я надеюсь, что это также облегчит работу с тензорами цветовой структуры , которые я хочу использовать в своем проекте.

23 августа 2014 года, в 20:36, jdbates [email protected] написал:

@JeffBezanson , я неоднозначно отношусь к обработке массивов как подмножеств тензоров. Таким образом, информация не теряется, но в то же время существует множество возможных интерпретаций изображений, а тензорная интерпретация не всегда (или даже обычно) имеет смысл. Рассмотрим изображения: изображение можно рассматривать как векторное поле на (обычно 2d) многообразии. Ограничение этого поля прямоугольной сеткой дает вам структуру, которую, естественно, вы хотите представить с помощью трехмерного массива. Однако на самом деле это просто отображение пространства точек сетки в векторное пространство {R, G, B}, поэтому геометрический смысл первых двух измерений (метки x и y сетки) отличается от геометрический смысл третьего измерения (которое, по сути, является вектором).

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

Я не против предложения @Jutho о разделении тензорной механики в отдельный пакет. Вероятно, он прав в том, что количество пользователей, которым нужна полная тензорная механика, намного меньше, чем количество людей, которым просто нужны простые операции с массивами. Вопрос, который мы действительно пытаемся здесь задать, состоит в том, «в какую область должна попадать линейная алгебра?»

Аппарат линейной алгебры - это достаточно существенное подмножество аппарата тензорной алгебры, так что, по крайней мере, на мой взгляд, нет смысла реализовывать первый без реализации второго. Такие операции, как v'M, будут представлены более кратко и последовательно, если у нас есть понятие ко- и контравариантных векторов, но это уже подводит нас к большей части пути к общим тензорным операциям.

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

существует несколько возможных интерпретаций массивов, а интерпретация тензора не всегда (или даже обычно) имеет смысл. Рассмотрим изображения: изображение можно рассматривать как векторное поле на (обычно 2d) многообразии. Ограничение этого поля прямоугольной сеткой дает вам структуру, которую, естественно, вы хотите представить с помощью трехмерного массива. Однако на самом деле это просто отображение пространства точек сетки в векторное пространство {R, G, B}, поэтому геометрический смысл первых двух измерений (метки x и y сетки) отличается от геометрический смысл третьего измерения (которое, по сути, является вектором).

Именно такое различие я пытался уловить в условном предложении AbstractTensorArray https://github.com/JuliaLang/julia/issues/4774#issuecomment -38333295, позволяя использовать как массив, так и тензор -подобные размеры. По этой схеме я бы ожидал представить ваш пример как

AbstractTensorArray{Uint8, 3, [false, true, false], [true, false, false]}

так что размеры x, y и RGB должны быть «вниз», «вверх» и «нейтрально» соответственно. Геометрические операции (например, аффинные преобразования) могут затем обрабатывать размеры координат сетки тензорным способом, отображая значения RGB в виде массива. (Если позже вы захотите обрабатывать значения RGB геометрически, вам придется явно изменить маску для этой цели, но я бы предположил, что (а) реже, что два разных вида геометрических операций будут применяться к разным подпространствам та же таблица данных, и (б) в этой ситуации явное преобразование, вероятно, _Улучшает_ ясность кода.)

Я не рассматривал сопряженные представления, которые упоминает @Jutho , но мне кажется, что с этим обобщением можно справиться, расширив тот же подход маскирования для сложных пространств.

Вопрос, который мы действительно пытаемся здесь задать, состоит в том, «в какую область должна попадать линейная алгебра?»

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

так что размеры x, y и RGB должны быть «вниз», «вверх» и «нейтрально» соответственно. Геометрические операции (например, аффинные преобразования) могут затем обрабатывать размеры координат сетки тензорным способом, отображая значения RGB в виде массива. (Если позже вы захотите обрабатывать значения RGB геометрически, вам придется явно изменить маску для этой цели, но я бы предположил, что (а) реже, что два разных вида геометрических операций будут применяться к разным подпространствам та же таблица данных, и (б) в этой ситуации явное преобразование, вероятно, улучшит ясность кода.)

Я думаю, вы здесь что-то смешиваете. В приведенном выше обсуждении было фактически объяснено, что координаты x и y не имеют интерпретации векторного пространства, поскольку они могут соответствовать координатам на произвольном изогнутом многообразии, не обязательно в плоском пространстве. Это было измерение RGB, которому была дана векторная интерпретация, хотя на самом деле это также может быть не лучший выбор, поскольку я, кажется, помню (у меня нет приличного фона в обработке изображений), что цветовое пространство также довольно искривлено. Кроме того, даже в случае, когда домен (x и y) образует векторное пространство, почему x и y должны быть вверх и вниз, или это просто пример вашей записи?

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

Координаты x и y не имели интерпретации векторного пространства

Извините, я перечитал "прямоугольную сетку" - думаю, @jdbates имел в виду именно то, что он сказал. Но разве мы не говорим просто о замене скалярных продуктов обобщенными внутренними продуктами? (Простите, если я неправильно понял, я провожу почти все свое время в евклидовом пространстве :-)

каждый тензор является элементом некоторого векторного пространства

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

У меня есть новое предложение по этому вопросу.


(1) Нарезка в стиле APL.

size(A[i_1, ..., i_n]) == tuple(size(i_1)..., ..., size(i_n)...)

В частности, это означает, что «одноэлементные срезы» - то есть срезы, в которых индекс является скалярным или нульмерным - всегда отбрасываются, а M[1,:] и M[:,1] являются векторами, а не одним вектором. в то время как другой - матрица-строка или любое другое подобное различие.


(2) Представьте типы оболочки Transpose и ConjTranspose для векторов и матриц. Другими словами, примерно так:

immutable Transpose{T,n,A<:AbstractArray} <: AbstractArray{T,n}
    array::A
end
Transpose{T,n}(a::AbstractArray{T,n}) = Transpose{T,n,typeof(a)}(a)

и все соответствующие методы, чтобы они работали так, как должны для векторов и матриц. Возможно, мы захотим ограничить его работой только с векторами и матрицами, поскольку неясно, что общее транспонирование должно означать для произвольных размеров (хотя простое изменение размеров заманчиво). Когда вы пишете a' вы получаете ConjTranspose(a) и аналогично v.' дает Transpose(a) .


(3) Определите различные специализированные методы для (сопряжения) транспонированных векторов и матриц, например:

*(v::Transpose{T,1}, w::AbstractVector) = dot(v.array,w)
*(v::AbstractVector, w::Transpose{T,1}) = [ v[i]*w[j] for i=1:length(v), j=1:length(w) ]

и т.д., включая замену всех ужасных функций At_mul_B и специального синтаксического анализа конструкцией ленивого (сопряженного) транспонирования с последующей отправкой по типам Transpose и ConjTranspose .


(4) Ограничьте широковещательные операции случаями, когда аргументами являются скаляры или массивы с одинаковым числом измерений. Таким образом, следующее, которое в настоящее время работает, как показано, не сработает:

julia> M = rand(3,4);

julia> M./M[1,:]
3x4 Array{Float64,2}:
 1.0       1.0       1.0      1.0
 0.516884  0.675712  2.11216  9.0797
 1.00641   0.726229  2.48336  4.38751

julia> M./M[:,1]
3x4 Array{Float64,2}:
 1.0  0.891557  0.561464  0.103968
 1.0  1.16552   2.29433   1.82633
 1.0  0.643353  1.38544   0.453257

Вместо этого вам придется сделать что-то вроде этого:

julia> M./M[[1],:]
3x4 Array{Float64,2}:
 1.0       1.0       1.0      1.0
 0.516884  0.675712  2.11216  9.0797
 1.00641   0.726229  2.48336  4.38751

julia> M./M[:,[1]]
3x4 Array{Float64,2}:
 1.0  0.891557  0.561464  0.103968
 1.0  1.16552   2.29433   1.82633
 1.0  0.643353  1.38544   0.453257

Я считаю, что это предложение решает все наши основные проблемы:

  1. симметричное поведение нарезки - конечные размеры больше не являются особенными.
  2. v'' === v .
  3. v' == v .
  4. v'w - это точечное произведение v и w - в частности, это скаляр, а не одноэлементный вектор.
  5. v*w' - это внешнее произведение v и w .
  6. M*v - вектор.
  7. M*v' - ошибка.
  8. v'*M - транспонированный вектор.
  9. v*M - ошибка.
  10. Операторы At_mul_B и специальный синтаксический анализ ушли.

: +1: ко всему. Я немного поработал над 2 и 3 в # 6837, но так и не закончил. @simonbyrne тоже изучил это.

+1 тоже. Похоже, это будет предлагать довольно последовательное поведение повсюду.

Единственная действительно разрушительная часть этого предложения на самом деле заключается в том, что M[1,:] является неявно вертикальным вектором, а не явно горизонтальной матрицей-строкой. В остальном, на самом деле, это довольно плавный набор изменений без прерывания работы (можно надеяться). Главное прозрение (для меня) заключалось в том, что поведение нарезки APL можно было комбинировать с ленивыми транспозами. Если мы получим поддержку, мы сможем разработать план и разделить работу. Я действительно надеюсь, что ленивое транспонирование и поэтапные функции позволят немного сократить и упростить код.

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

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

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

@Stefan : Кажется, неплохая идея проработать вектор и 2-мерный
алгебра. Всего несколько задач:

  1. Что касается случаев многомерных массивов: для массива A с размерностью
    (i_1, i_2, ..., i_n), если нужно применить транспонирование к [i_2, i_3]
    размеры - или даже хеш для измерений [i_2, i_4]. Вы можете сделать это в
    новое определение транспонирования?
  2. Что касается одноэлементного измерения: возможно, что одноэлементный срез
    ушел намеренно. Следует ли Джулии сохранить это одноэлементное измерение после
    расчет? Например, если определить вектор как массив V в
    размерности (2,1), и хочет умножить транспонирование на матрицу A в
    размерность (2,3,4). Можете ли вы дать результат v '* A в измерении
    (1,3,4)?

В чт, 16 октября 2014 г., в 14:31, Стефан Карпински [email protected]
написал:

Единственным нарушением может быть то, что M [1 ,:] является (вертикальным) вектором
а не матрица-строка. В противном случае это на самом деле довольно гладко,
набор изменений без нарушения работы (можно надеяться). Главное прозрение (для меня) было
что поведение нарезки APL можно комбинировать с ленивым транспонированием. Если мы получим
В результате мы можем составить план и разделить работу. я действительно надеюсь
что ленивое транспонирование и поэтапные функции позволяют немного сократить код и
упрощение.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -59425385.

По поводу 2 и 3: попробовав это, я пришел к выводу, что транспонирование вектора НЕ должно быть подтипом AbstractVector , иначе все будет запутано (см. Обсуждение # 6837). Я думаю , что самый разумный путь вперед с Transpose{T,A} <: AbstractMatrix{T} , а отдельный Covector Тип (+ Conjugate варианты).

Другая важная проблема, с которой я столкнулся, заключается в том, что часто вы хотите выполнить диспетчеризацию для определенного типа матрицы, ее транспонирования или сопряженного транспонирования. К сожалению, я не смог придумать способ выразить это с помощью существующего механизма типов (см. Обсуждение в этом списке рассылки ). Без этого, боюсь, нас ждет много @eval за 3x3 возможных комбинации аргументов.

@simonbyrne , я

Я указал (на менее публичных форумах, поэтому здесь, вероятно, стоит упомянуть здесь вкратце), что потенциальной альтернативой является обработка всего формирования _ внутренне_, путем расширения типов индексов, которые могут использовать SubArrays. В частности, можно иметь тип «транспонированный диапазон», который придавал бы транспонированную форму SubArray, даже если родительский массив - это Vector . (См. Https://github.com/JuliaLang/julia/blob/d4cab1dd127a6e13deae5652872365653a5f4010/base/subarray.jl#L5-L9, если вы не знакомы с тем, как / могут быть реализованы подмассивы.)

Я не уверен, облегчает ли эта альтернативная стратегия жизнь или затрудняет ее. Это уменьшает количество типов, обращенных извне, что может означать, что нужно меньше методов. (Для человека, который _прежнему__ заполняет недостающие методы из-за перехода Color в Images , это кажется хорошей вещью.) С другой стороны, при отсутствии удобной треугольной отправки это могло сделать несколько более неудобным написание выборочных методов, которые могут усугубить проблемы, поднятые @simonbyrne.

Любые идеи приветствуются.

Помимо таких деталей, мне нравится форма предложения @StefanKarpinski . Я не привязан к индексированию в стиле APL, но в целом подозреваю, что это лучший выбор, чем правила, производные от Matlab, которые у нас есть сейчас.

Две мысли:

  • Если индексирование, такое как A[[2], :] становится идиоматическим, кажется немного расточительным создавать вектор только для обертывания единственного индекса 2 . Следует ли нам разрешить A[(2,), :] для того же или чего-то подобного? Я думаю, что диапазон из одного элемента - это нормально, но было бы неплохо иметь для него синтаксис, почти такой же удобный, как [2] .
  • Если для широковещательной передачи нам потребуется соответствующее количество измерений, должен быть простой способ добавить в массив одноэлементные измерения, возможно, что-то вроде индексации numpy newaxis .

Я думал о том, чтобы предложить, чтобы индексирование с помощью точки с запятой, а-ля A[2;:] , могло быть другим режимом индексирования, в котором результат всегда имеет то же количество измерений, что и A - т.е. не отбрасывать одиночные символы и индексирование с все, что имеет ранг больше единицы, является ошибкой. Решил оставить это вне основного предложения для простоты, но что-то в этом роде кажется неплохим.

Я вижу озабоченность, выраженную @simonbyrne . Однако, в принципе, ковектор - это также просто вектор, живущий в другом векторном пространстве, а именно в дуальном пространстве. Так что превращение типа Transpose или Covector в подтип AbstractArray тоже несколько неприятно. Возможное решение, которое будет серьезным критическим изменением и, вероятно, не будет рассматриваться (но я все равно хотел бы упомянуть об этом), заключается в том, чтобы дать всему семейству AbstractArray дополнительный параметр типа trans , которые могут иметь значения :N , :T или :C . Для всех методов, которые просто предполагают, что вектор является одномерным списком чисел, им не нужно различать разные значения этого последнего параметра, и поэтому соответствующие определения методов могут оставаться такими, какие они есть сейчас.

Для N-мерных массивов с N> 2 существуют различные варианты. Либо transpose выдает ошибку, и невозможно создать объект типа AbstractArray{3,Float64,trans} где trans!=:N , либо, в качестве альтернативы, :T просто означает строковое значение и transpose общего массива имеет эффект инвертирования всех измерений. Я думаю, что последнее также является принятым соглашением тех людей, которые используют графическую нотацию Пенроуза (см. Http://en.wikipedia.org/wiki/Penrose_graphical_notation, хотя транспонирование там не объясняется, но также см. Цитируемую книгу Цвитановича).

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

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

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

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

@toivoh ,

  • A[2:2,:] также сохранит измерение, и это не требует выделения или какого-либо нового синтаксиса.
  • Что-то вроде newaxis кажется в высшей степени возможным. Действительно, с архитектурой в # 8501 кажется возможным создать широковещательную рассылку напрямую путем индексации: иметь тип индекса, который всегда разрешается в 1, независимо от того, какое значение пользователь заполняет в этом слоте.

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

Очень хорошее предложение: +1 :.

Напомните мне, зачем нам v' == v ?

На самом деле нам это не нужно, но это неплохо, поскольку двойственное (конечномерное) векторное пространство изоморфно ему.

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

Я не уверен, что мы хотим v '== v, но я думаю, что это довольно ортогонально
остальные. Хотим ли мы, чтобы матрица столбцов и вектор сравнивались равными, если они
иметь равные элементы?

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

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

Имеет ли смысл convert что-то от 1-го до 2-го, добавляя конечное одноэлементное измерение?

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

Одна вещь, которую я заметил в # 8416, заключается в том, что сейчас sparsevec беспорядочно подделывается как одноколоночная матрица CSC. Sparse должен достаточно хорошо вписаться в это, как только будет реализован правильный одномерный разреженный векторный тип (который выпадет как простейший полезный случай общего типа Nd COO, его просто нужно написать).

Просто принимаю все это. Значит, следующее не сработает?

A [1 ,:] * A * A [:, 1] # строка из столбца Matrix * Matrix * из матрицы ???

Вы написали

v'w - это скалярное произведение v и w, в частности, это скаляр, а не одноэлементный вектор.

Также v '* w - скаляр?

Мне нравится идея точки (x, y) принимать любые два элемента формы (1, ..., 1, m, 1, ..., 1) и
возврат скалярного продукта несмотря ни на что. Но я не хочу, чтобы x * y давал точку (x, y) в этом смысле
если x не ковектор, а y вектор.

Не уверен, что это такая горячая идея, но, может быть, было бы хорошо, если бы
A [:, 1,1] был вектором, а A [1,:, 1] или A [:, 1,:] были ковекторами.
Лучше проследить размерность вектора - слот, на котором вы
разрешено сжимать тензор, при этом стандартная линейная алгебра
1 (векторы-строки) и 2 вектора-столбца.

На мой взгляд, двумя основными проблемами, которые мы ранее рассматривали в этом вопросе, были:

(A) как различать тензорную семантику (для сокращений) и семантику массива (для широковещательной передачи) при работе с многомерными данными;
(B) как встроить очевидную и удобную линейную алгебру в согласованную структуру, которая распространяется на более высокие измерения.

Мне не ясно, как это предложение решает любую из этих проблем. Насколько я могу судить, для достижения (A) по-прежнему требуются специальные пользовательские манипуляции (как с современной функциональностью); и для адресации (B) с использованием ленивых оберток потребуется что-то вроде расширений SubArray , предложенных List ), но во всех этих случаях мне кажется, что лень должна быть необязательной стратегией.

Я не знаю, сколько из них разделяет мнение @Jutho о том, что «тензорные сокращения более высокого порядка не поддерживаются (и не должны поддерживаться) посредством обычного умножения в любом случае», но я не мог не согласиться с этим: я делаю только то, что считаю обычной инженерией. математика, и они мне нужны все время. Хотя современные языки, такие как Mathematica и NumPy, имеют свои ограничения дизайна в этом отношении (как я уже говорил выше), они, по крайней мере, поддерживаются! Например, как только вы захотите использовать градиент векторного поля в простом численном методе, вам понадобятся тензорные сокращения более высокого порядка.

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

Что-нибудь в этом предложении _конфликтует_ с улучшением ваших пунктов (A) и (B)?

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

Мне тоже все время нужны тензорные сокращения в их наиболее общей форме, именно поэтому я знаю, что определение наиболее общего тензорного сжатия, не говоря уже о его эффективной реализации, не совсем проста. Вот почему я утверждал, что для этого нужны специальные функции, а не пытаться втиснуть некоторую частично работающую или, скорее, конкретную функциональность в стандартные операторы в базе Julia, которые не покрывают половину вариантов использования. Но я рад изменить свое мнение, например, есть ли одно «стандартное» сокращение, которое намного важнее / полезнее любого другого? Но это может сильно зависеть от предметной области и, следовательно, не подходить в качестве общего правила для внедрения в Julia Base.

Оп 19-окт.-2014 в 22:52 heeft thomasmcoffee [email protected] het volgende geschreven:

На мой взгляд, двумя основными проблемами, которые мы ранее рассматривали в этом вопросе, были:

(A) как различать тензорную семантику (для сокращений) и семантику массива (для широковещательной передачи) при работе с многомерными данными;
(B) как встроить очевидную и удобную линейную алгебру в согласованную структуру, которая распространяется на более высокие измерения.

Мне не ясно, как это предложение решает любую из этих проблем. Насколько я могу судить, для достижения (A) по-прежнему требуются специальные пользовательские манипуляции (как с современной функциональностью); и для адресации (B) с использованием ленивых оберток потребуется что-то вроде расширений SubArray , предложенных

Я не знаю, сколько из них разделяет мнение @Jutho о том, что «тензорные сокращения более высокого порядка не поддерживаются (и не должны поддерживаться) посредством обычного умножения в любом случае», но я не мог не согласиться с этим: я делаю только то, что считаю обычной инженерией. математика, и они мне нужны все время. Хотя современные языки, такие как Mathematica и NumPy, имеют свои ограничения дизайна в этом отношении (как я уже говорил выше), они, по крайней мере, поддерживаются! Например, как только вы захотите использовать градиент векторного поля в простом численном методе, вам понадобятся тензорные сокращения более высокого порядка.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

вот сокращение последнего индекса A и первого индекса B
что-то вроде точки математики

function contract(A,B)
   s=size(A)
   t=size(B)
   reshape(reshape(A, prod(s[1:end-1]), s[end]) *  reshape(B,t[1],prod(t[2:end])) , [s[1:end-1]... t[2:end]...]...)
end

Мне всегда удавалось выполнять общие сокращения с изменениями форм, перестановками и, возможно, сложными
конъюгирует при необходимости более или менее как указано выше

не уверен, в чем на самом деле большая проблема с тензорами, почему мы не можем просто реализовать некоторые из этих
функции?

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

  1. Какие параметры отбрасывать при индексации? «Стиль APL» кажется бесспорным.
  2. Что дает vector' ?

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

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

Меня больше всего беспокоит то, что

(взять строку из 2d массива) * (2d массив) * (взять столбец из 2d массива)

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

@JeffBezanson , когда я говорю, что эти операции поддерживаются, я имею в виду, что встроенные типы данных и функции специально разработаны с учетом них, например, как функция Dot Mathematica. Таким образом, для пользователя существует встроенный, документированный и / или очевидный способ делать определенные вещи. Для любого дизайна можно добиться поддержки чего угодно, добавляя функции, как и в текущей реализации; так что это не проблема технического конфликта, это проблема дизайна.

@Jutho , я мало использую MATLAB, поэтому не могу комментировать. Я согласен с тем, что дизайн NumPy менее согласован, чем дизайн Mathematica (как я обсуждал выше), но он также поддерживает более широкий диапазон поведений. Я согласен с тем, что базовая линейная алгебра должна оставлять общий тензорный механизм невидимым для пользователей, которым он не нужен, но из-за потрясающих языковых особенностей Julia не кажется необходимым вводить для них разные реализации, поскольку и NumPy, и Mathematica имеют были вынуждены делать до некоторой степени. Мне казалось, что эта проблема, по крайней мере частично, связана с поиском правильной унифицированной системы для обоих, чтобы показать, какие специализации следует использовать для общих случаев линейной алгебры: например, что делать с vector' .

A [1 ,:] * A * A [:, 1] # строка из столбца Matrix * Matrix * из матрицы ???

Правильно - вам нужно будет написать A[1,:]' * A * A[:,1] .

Также v '* w - скаляр?

Да, v'w - это одно и то же. Одно из преимуществ этого предложения состоит в том, что оно полностью исключает дешевые синтаксические взломы.

Не уверен, что это такая горячая идея ...

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

@thomasmcoffee Просто будьте более конкретны. Конечно, все хотят, чтобы вещи были последовательными, задокументированными, очевидными и т. Д. Вопрос в том, служит ли представленное предложение этим целям? Может быть, текущее предложение вообще не влияет на эти цели, и это нормально - тогда, пока оно ведет к улучшению в другом месте, у нас все еще есть чистое улучшение.

Просто дай мне понять это правильно

Если A не квадрат

| | Текущий | Предлагается | MATLAB |
| --- | --- | --- | --- |
| A * A [1 ,:] | Нет | Да | Нет |
| A * A [1 ,:] '| Да | Нет | Да |
| A [:, 1] A | Нет |
A [:, 1] ' A | Да | Да | Да |

и если A квадратная

| | Текущий | Предлагается | MATLAB |
| --- | --- | --- | --- |
| A * A [:, 1] | Да | Да | Да |
| A * A [:, 1] '| Нет | Нет | Нет |
| A [1 ,:] A | Нет |
A [1 ,:] ' A | Нет | Да | Нет |

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

Было бы хорошо, если бы

dot(x,y) и dot(x.',y) и dot(x,y.') и dot(x.',y.') все дают один и тот же скаляр?

т.е. Σᵢ con (xᵢ) * yᵢ

таким образом можно сделать точку (x, A * y), не особо задумываясь

Эти примеры @alanedelman кажутся немного обратными транспонированием в тех местах, которые вам не должны быть из-за индексации APL. Может быть, этого достаточно для того, чтобы иметь вариант индексации с сохранением размеров, как я думаю, обсуждалось (например, A[i; :] ?)

Но тогда вы захотите получить ковектор в приведенном выше случае.

@alanedelman , я не вижу причин, по которым эти методы dot не должны существовать и давать тот же результат.

Мне всегда удавалось выполнять общие сокращения с изменениями форм, перестановками и, возможно, сложными
конъюгирует при необходимости более или менее как указано выше

Именно так это реализовано в функции tensorcontract в TensorOperations.jl, если вы выберете метод: BLAS, который, безусловно, является самым быстрым для больших тензоров. Я также написал собственную реализацию julia, используя функциональность Cartesian.jl (и, надеюсь, однажды с использованием поэтапных функций), которая устраняет необходимость в permutedims (дополнительное выделение памяти) и быстрее для меньших тензоров.

Я просто отвечал на ложное утверждение, что Matlab предоставляет для этого встроенные функции, которых нет у Джулии. И reshape, и permuteims доступны в Julia. В Numpy действительно есть функция tensordot, которая делает именно это, но не позволяет вам указывать порядок индекса выходного тензора, поэтому вам все равно понадобится permutedims после этого, если вы имели в виду определенный порядок вывода.

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

+1 за предложение Стефана. Кажется, что он дает предельно ясную, но достаточно гибкую семантику. Как пользователь линейной алгебры, даже привыкший к синтаксису стиля MATLAB, я считаю правила достаточно простыми для использования.

Я немного смущен тем, что в целом означает ' . Если v - вектор, v' - это transpose . Если a - это двумерный массив, то a' теперь также будет transpose . Похоже, что они оба определены в интересах возможности легко сформировать b' * a , связав первое измерение b с первым измерением a .

Похоже, что нет единого мнения об определении a' когда размерность a > 2. Я не слышал, чтобы кто-либо предлагал что-либо, кроме изменения размеров, и это совпадает с тем, что b' * a сжимает первое измерение b с первым измерением a .

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

Кажется разумным иметь другую функцию сжатия, доступную в Base для более общих ситуаций, например, contract(a, b, 2, 3) чтобы сократить 2-е измерение a с 3-м из b .

Кстати, dot(a,b) == a'*b когда a и b - векторы, но dot(a,b) в настоящее время не определено для матриц. Можно нам dot(a,b) = trace(a'*b) ?

@madeleineudell : Я немного не понимаю, что в целом должно означать.

Я разделяю это беспокойство. Вы можете в основном взять свойства 2-5, 7, 8 и 10 в моем предложении в качестве определяющих характеристик. Т.е. вы хотите, чтобы это удерживалось:

  • v' одномерный, но не вектор
  • v'' === v
  • v' == v
  • v'w - скаляр
  • v*w' - матрица
  • v'*M - это то же самое, что v'
  • M' - двумерный объект, но не матрица, возможно, матричный вид

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

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

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

20 октября 2014 года в 09:05 toivoh [email protected] написал:

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

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

Я бы сказал, что это неверно, если вы хотите думать об общих тензорах. Это может быть правдой, если вы думаете только о матрицах и векторах как о некоторых блоках с числами в них, и вы думаете о v как о столбце и v 'как о строке.

Однако, если вы думаете о v как о некотором элементе векторного пространства, а w = v 'как о некотором способе сопоставить v с элементом w двойного пространства V_, тогда w все равно будет вектором, то есть одномерным объектом. В общем, нужна метрика для определения этого отображения из V в V_, то есть w_i = g_ {i, j} v ^ j. Теперь, если V - декартово пространство, то есть R ^ n со стандартной евклидовой метрикой, то V * естественно изоморфно V. Это означает, что нет понятия верхних или нижних индексов (ковариантных или контравариантных) и, следовательно, w_i = v_i = v ^ я = w ^ я. Я бы сказал, что это тот случай, который используется в большинстве программ, т.е. случай, который должен поддерживаться Julia Base. (Для комплексного векторного пространства V * естественно изоморфно Vbar, сопряженному векторному пространству, и, следовательно, естественное отображение w_i = con (v ^ i).)

Соглашение об обозначении векторов как столбцов с числами, двойных векторов как строк с числами и, следовательно, матриц (которые являются элементами V \ otimes V_) как блоков с числами, чрезвычайно удобно, но прекращается, как только вы также хотите рассмотреть более многомерные массивы, т.е. элементы пространств тензорных произведений более высокого порядка. В этом случае «вектор-строка», то есть двумерный объект, где первое измерение имеет размер 1, выражаясь терминологией Matlab / julia, является некоторым элементом пространства тензорного произведения V1 \ otimes V2, где V1 просто случайно быть R (или другим одномерным пространством). Я согласен с тем, что это может быть не то, что вы хотите в качестве поведения по умолчанию, но я бы предпочел, чтобы просто не пытались определять транспонирование для общего массива и ссылаться на permutedims, как это делает Matlab. Обращение первых двух измерений для общего тензора в качестве соглашения по умолчанию не имеет смысла. Перемещение элемента из некоторого пространства тензорного произведения высшего порядка V1 \ otimes V2 \ otimes… \ otimes Vn не имеет однозначного определения. Соглашение об обратном изменении всех измерений просто проистекает из удобного графического представления Пенроуза, как упоминалось выше. Кроме того, это тот, который отображает матричное пространство (V \ otimes V_) на себя (V * \ otimes V * = V \ otimes V ).

Я вижу два пути вперед:
1) Заставьте удобный оператор (и, возможно, даже *) работать с произвольными массивами более высокого порядка, используя какое-то выбранное соглашение, в пределах настройки декартовых тензоров (т.е. без различия между верхними и нижними индексами). Это может привести к неожиданным результатам для типичных случаев использования.

2) Ограничить 'и * векторами и матрицами. Ошибка в массивах более высокого порядка. Я думаю, что это самый популярный подход (например, Matlab и т. Д.).

Этот пост немного отличается от позиции, которую я занял в прошлом году, чтобы

исследуйте амбивалентность с обеих сторон.

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

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

ndim (А) <= 2? interchange_first_two_dims: no_op

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

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

Я поигрался с апострофом-звездой ("'*") (если возможно! Или что-то еще, если невозможно)
должен означать сокращение последнего измерения до первого измерения (например, точка в системе Mathematica)
и иметь индексацию APL и никаких ковекторов. Часть моего мозга все еще думает, что это стоит изучить,
но когда я просыпаюсь, нынешний подход кажется все лучше и лучше.
(Посмотрим, как я себя чувствую сегодня позже)

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

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

Могу сказать, что меня часто раздражало

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1; 2; 3] # ndims == 1

в основном потому, что я не могу этого вспомнить.

Просто мысленный эксперимент. Какие функции будут потеряны, если мы переопределим * чтобы он служил оператором сжатия, подобным тому, что @alanedelman имеет в виду для '* ? Разве это не устранило бы необходимость в ' в линейных алгебраических операциях (кроме работы в качестве транспонирования для матриц)? Я только вижу, что это лишило бы нас внешнего продукта w*v' , который можно было бы легко заменить на outer(w,v) .

РЕДАКТИРОВАТЬ: Предполагая, что нарезка APL есть. Например, A[1,:]*A*A[:,1] будет иметь ожидаемый результат без необходимости транспонирования первого операнда с помощью ' .

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

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

В некоторой степени не связаны, но не совсем не связаны ....
Я хотел бы отметить, что точка-звезда, которую все читают как точка-звезда, изначально (как мне сказали) была
означало быть точечной звездой, потому что оператор ТОЧКА
имел в виду

Могу сказать, что меня часто раздражало

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1; 2; 3] # ndims == 1

в основном потому, что я не могу этого вспомнить.

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

Какие функции будут потеряны, если мы переопределим *, чтобы он служил оператором сжатия, подобным тому, что @alanedelman имеет в виду для '*?

Мы потеряем ассоциативность: например, (M*v)*v даст текущий dot(v,M*v) (скаляр), тогда как M*(v*v) даст M.*dot(v,v) (матрицу).

Почему бы нам просто не сделать dot оператором сжатия (который в любом случае не ассоциативен)? Мы также можем определить сокращения более высокого порядка, например, ddot(A::Matrix,B::Matrix) == A⋅⋅B == trace(A'*B) .

Итак, правильно ли я понимаю, что единственная цель введения векторных транспозиций - спасти нас от неассоциативности * ? Неоднозначность примера @alanedelman можно исправить, переставив один из векторов ( M*v*v' vs M*v'*v ), но то же самое можно сделать и с помощью скобок ( M*(v*v) vs (M*v)*v ) без всех хлопот по реализации транспонирования для векторов (как уже отмечал @Jutho , реализация транспонирования для тензоров более высокого порядка в любом случае не имеет математического смысла). Для меня вопрос в том, какие обозначения более читабельны / лаконичны / элегантны / чисты.

На самом деле M*(v*v) и (M*v)*v в настоящее время анализируются как

*(M, v, v)

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

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

(как уже отмечал @Jutho , реализация транспонирования для тензоров более высокого порядка в любом случае не имеет математического смысла).

Я не думаю, что это именно то, что я сказал.

Какие функции будут потеряны, если мы переопределим *, чтобы он служил оператором сжатия, подобным тому, что @alanedelman имеет в виду для '*?

Мы потеряем ассоциативность: например, (M_v) _v даст текущую точку (v, M_v) (скаляр), тогда как M_ (v_v) даст M._dot (v, v) (матрицу).

Почему бы нам просто не поставить точку на операторе сжатия (который в любом случае не ассоциативен)? Мы также можем определить сокращения более высокого порядка, например ddot (A :: Matrix, B :: Matrix) == A⋅⋅B == trace (A '* B).

С этой цепочкой рассуждений нам просто больше не нужен оператор умножения для векторов и матриц, мы можем просто записать все в терминах точки:
А ∙ Б
A ∙ v
v ∙ A ∙ w
v ∙ w

Это всего лишь предложение

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

Думаю, отчасти проблема в том, что даже в математике существуют разные соглашения. Думает в терминах математики векторов строк и столбцов, или мы хотим более абстрактного поведения в терминах линейных операторов и двойных пространств (где я предполагаю, что оператор не умножается на вектор, а применяется к вектору, так почему бы не написать A (v) вместо A * v или A ∙ v)?

Мне не очень нужен удобный синтаксис, я лично предпочитаю писать точку (v, w) вместо v '* w каждый раз, так как первая более четко выражает базисно-независимую математическую операцию (фактически, сначала нужно определить может быть определено скалярное произведение перед естественным отображением векторов в двойственные векторы). =

Извините за такое невежество, но почему именно M[1, :] может быть транспонированным, ведя себя как v' ?

Я собираюсь добавить в свой список то, что меня беспокоит

1.

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1;2;3] # ndims == 1

2.
v=rand(3)
v' * v имеет ndims == 1 (предложение @StefanKarpinski это исправляет)
(v' * v)/( v' * v ) имеет ndims ==2 (это действительно меня беспокоит, и это тоже будет исправлено)

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

Мне определенно нравится идея cdotсжатие последнего индекса с первым индексом в дополнение к оператору *
и разрешение точки (a, b, ..) допускает очень общие сокращения.

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

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

На мой взгляд, точка тоже должна возвращать скаляр.

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

Извините за такое невежество, но почему именно M [1,:] не может быть транспонированным, ведя себя как v '?

Может, но как это обобщить?

@Jutho , извини, я должен был сформулировать это иначе. Я имел в виду вашу строку «Транспонирование элемента из некоторого пространства тензорного произведения высшего порядка [...] не имеет уникального определения», поэтому нет математической мотивации для введения одного конкретного определения или для определения его вообще.

@alanedelman :

Я собираюсь добавить в свой список то, что меня беспокоит

[1 2 3] # ndims == 2
[1,2,3] # ndims == 1
[1; 2; 3] # ndims == 1

Конкатенационное поведение не связано с этой проблемой. Не будем мутить воду дальше.

Мне определенно нравится идея `cdot сжимать последний индекс с первым индексом в дополнение к оператору *
и разрешение точки (a, b, ..) допускает очень общие сокращения.

Это тоже тангенциально. Давайте сосредоточимся на массивах и нарезке.


В текущем подходе все массивы бесконечномерны с подразумеваемыми отброшенными единицами.

Это не совсем так. Если бы это было так, не было бы различия между ones(n) , ones(n,1) , ones(n,1,1) и т. Д. Но это все разные объекты с разными типами, которые даже не равны каждому. разное. Мы прилагаем некоторые усилия, чтобы реализовать такие вещи, чтобы они вели себя _подобно_, но это далеко от массивов, фактически являющихся бесконечномерными.


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

  1. асимметричное поведение нарезки - конечные размеры особенные.
  2. v'' !== v - на самом деле v'' != v ; это потому, что у них разный ранг.
  3. v' != v - такая же сделка.
  4. v'w - это одноэлементный вектор, а не скаляр.
  5. нам нужен специальный синтаксический анализ для A*_mul_B* , что является самой ужасной вещью на свете.

Я согласен с большинством пунктов @StefanKarpinski , за исключением бита v' == v : я не думаю, что они должны быть равными. Да, они могут быть изоморфными, но они по-прежнему являются разными объектами, поскольку ведут себя по-разному: матрицы M и M' также изоморфны, но я не думаю, что мы когда-либо захотим, чтобы они были равны (если, конечно, они не эрмитские).

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

+1 к мнению @simonbyrne , включая его общее одобрение точки зрения

Также согласен с @simonbyrne.

Да, v и v' имеют разные типы, но мы уже считаем, что разные типы массивов с одинаковой формой и данными равны - например, speye(5) == eye(5) . Почему ковектор отличается от разреженного?

Я надеюсь, что ковекторы будут транслироваться как матрицы строк. Для массивов A и B , которые считаются равными, до сих пор выполняется

all(A .== B)

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

Для меня ковектор больше похож на матрицу-строку, чем на векторную или столбцовую матрицу.

Думаю, ключевой вопрос в том, что такое size(v' )? Если ответ - (length(v),) тогда я думаю, что v == v' должно быть правдой. Если size(v') == (1,length(v)) то, вероятно, оно должно быть ложным, но возможно тогда v' == reshape(v,1,length(v)) должно быть истинным. Итак, вопрос в том, должен ли v' быть вектором особого типа или особой матрицей?

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

Ковекторы на самом деле не являются массивами, поэтому я не думаю, что мы действительно можем сказать, какую «форму» они имеют. Ковектор действительно определяется тем, что он делает, то есть *(::Covector, ::Vector) дает скаляр: поскольку любой AbstractVector этого не делает, это не совсем то же самое.

Другая проблема может быть в сложном поле: будет ли у нас v' == v или v.' == v ?

@simonbyrne :

Ковектор действительно определяется тем, что он делает, то есть *(::Covector, ::Vector) дает скаляр: поскольку любой AbstractVector этого не делает, это не совсем то же самое.

Это действительно хороший момент. Однако было бы довольно неприятно, если бы v' нельзя было использовать как объект. Возможно, правильный подход - рассматривать v' как забавную матрицу-строку вместо забавного вектора.

Возможно, правильный подход - рассматривать v 'как забавную матрицу-строку вместо забавного вектора.

Вроде, но я тоже не думаю, что это должен быть подтип AbstractMatrix : я думаю, что AbstractCovector должен быть типом верхнего уровня. Я был бы счастлив определить length(::Covector) , но я не думаю, что мы должны определять метод size .

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

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

20 октября 2014 г. в 17:39 Саймон Бирн [email protected] написал:

Я согласен с большинством пунктов @StefanKarpinski , за исключением бита v '== v: я не думаю, что они должны быть равными. Да, они могут быть изоморфными, но они по-прежнему являются разными объектами, поскольку ведут себя по-разному: матрицы M и M 'также изоморфны, но я не думаю, что мы когда-либо захотим, чтобы они были равны (если, конечно, они не эрмитовы).

Это утверждение не имеет смысла ни на каком уровне.

1), я думаю, вы злоупотребляете здесь значением изоморфности. Изоморфность - это отношение между двумя пространствами (в данном случае). В общем, для любого (действительного) векторного пространства V существует двойственное пространство V * линейных функций (для комплексных пространств существует также сопряженное пространство и двойственное сопряженное пространство). Как правило, это разные пространства, и нет даже уникального или естественного отображения одного в другое, то есть вообще нет смысла связывать элементы v из V с элементами phi из V *. Единственная общая операция - это применение линейной функции, т.е. phi (v) является скаляром.

Естественное отображение из V в V * может быть определено, если у вас есть билинейная форма V x V -> скаляры, обычно внутренний продукт / метрика. Затем можно определить phi_i = g_ {i, j} v ^ j.

2), на самом деле нет естественной операции, связанной с «транспонированием вектора» (см. Пункт 3). Эта путаница возникает из-за идентификации векторов с матрицами столбцов.
Выражаясь, вероятно, слишком сильным, я действительно хотел бы увидеть какой-либо вариант использования транспонирования вектора, который вы не можете получить с помощью точки и, возможно, некоторой операции внешнего произведения / тензора?

Однако, если вы хотите использовать транспонирование как способ сопоставления векторов с двойными векторами, то есть сопоставить v в V с некоторым phi = v 'в V_, то вы предполагаете, что работаете со стандартным евклидовым внутренним произведением (g_ { i, j} = delta_ {i, j}). В этот момент вы стираете различие между ковариантными и контравариантными индексами, вы по существу работаете с декартовыми тензорами, и V и V_ становятся естественным образом изоморфными. Как указано выше, w_i = v ^ i = v_i = w ^ i, так что да, v == w, и я бы даже сказал, что нет ничего, что отличает эти два.

3) Операция «транспонирование» изначально определена только для линейных карт из V -> W (http://en.wikipedia.org/wiki/Dual_space#Transpose_of_a_linear_map), и на самом деле даже там она может не означать то, что вы думаете. Транспонирование линейного отображения A: V-> W - это отображение A ^ T из W _-> V_, то есть оно действует на векторы в W_, на двойственные векторы и производит элементы V_, то есть ковекторы V. Это означает, если вы думаете об этом с точки зрения обычного транспонирования A ^ T матрицы A, что эта матрица A ^ T должна быть умножена на столбцы, представляющие векторы в W *, и что столбец, который выходит из этого, представляет собой ковектор V Итак, на этом этапе идентификация двойных векторов с векторами-строками уже не удается.

Однако в типичном случае использования вещественных векторных пространств, где V * и W * отождествляются с V и W через стандартное евклидово внутреннее произведение, транспонирование линейной карты может быть отождествлено с сопряженным к этой линейной карте, что действительно карта из V-> W, как думает большинство людей, также подходит для транспонирования карты. В сложном случае это не удается, поскольку обычное транспонирование матрицы (без комплексного сопряжения) имеет смысл только как отображение W _-> V_, как отображение W-> V оно не является базисно-независимым определением и, следовательно, не имеет смысла в операционном отношении.

Что касается того, что транспонирование должно означать для массивов более высокой размерности, в рамках подхода Matlab / Engineering, к которому мы приближаемся: это должно быть ошибкой, это не обобщает. Это не значит, что его невозможно определить. Проблема в том, что представляет собой массив более высокого порядка. Является ли это элементом пространства тензорного произведения V1 \ otimes V2 \ otimes… \ otimes VN, является ли это полилинейным отображением, действующим на V1 x V2 x… x VN, является ли это линейным отображением некоторого тензорного пространства произведения V1 \ otimes V2 \ otimes … \ Время VN в другое тензорное пространство произведения W1 \ время W2 \ время… \ время WM? Для двумерного случая есть разрешение. Идентификация линейных отображений A: V-> W с векторами из W \ otimes V_, транспонирование в смысле линейного отображения соответствует обращению пространств в этом пространстве тензорного произведения, A ^ i_j -> A_j ^ i с A ^ T в V_ \ otimes W = V * \ otimes W _, что действительно является отображением из W_ -> V. Обращение размеров к объектам более высокого порядка хорошо тем, что оно имеет удобное графическое представление.

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

В этом отношении существует некоторая асимметрия, поскольку термин «матрица» гораздо менее перегружен, даже с математической точки зрения матрица - это просто удобное представление линейной карты в определенном базисе. Обратите внимание, что есть также много случаев, когда вы не хотите представлять линейную карту как матрицу, а скорее как функцию (эта проблема возникала раньше, например, в аргументах eigs и т. Д.). В этом отношении я могу понять, почему Matlab даже не позаботился о векторе, действительно одномерной структуре. При таком подходе все интерпретируется просто как «матрица», то есть в любом случае блок с числами.

Вопрос? Пусть c - ковектор. Что такое fft (c)?

Ответ: fft (c ')', который принимает сложные конъюгаты потенциально неожиданными способами
по сравнению с fft (c)

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

Бьюсь об заклад, есть еще много таких вещей

Я подозреваю, что сейчас правильнее всего в Base определить:

  • ковектор умножить на другой вектор
  • ковектор, умноженный на матрицу
  • covector ', чтобы получить вектор

+1 к минимальной поддержке ковекторов на данный момент.

Да, +1.

Так что, если я упомянул это, я почти уверен, что
norm (covector, q) должен быть norm (vector, p), где 1 / p + 1 / q = 1
из неравенства Холдера, это долго не будет реализовано. :-)

Слава богу за p = q = 2

Это было бы несложно реализовать:

norm(c::Covector, q::Integer) = norm(c.vector, q/(1-q))

Возможно, вам понадобится дополнительная проверка, чтобы избежать q == 0 и q == 1 .

Я думаю, что q == 1 было бы хорошо

Мед Венлиг hilsen

Андреас Ноак

2014-10-22 15:19 GMT-04: 00 Стефан Карпински [email protected] :

Это было бы несложно реализовать:

norm (c :: Covector, q :: Integer) = norm (c.vector, q / (1-q))

Возможно, вам понадобится дополнительная проверка, чтобы избежать q == 0 и q == 1.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -60139762.

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

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

Точнее:

  • Пусть V = V(F) и W = W(F) - векторы над некоторым полем элементов F ,
  • v и w - векторы, которые являются элементами V и W соответственно,
  • <.,.> - внутренний продукт, такой что <.,.> : V × W → F и v, w ↦ <v, w>

Ковектор, соответствующий v является линейным функционалом v' : W → F который выполняет отображение w ↦ <v, w> .

Обычное описание заканчивается тем, что v' - это элемент двойного пространства V* при этом больше не говорится о том, что двойное пространство _is_.

Для специалистов по информатике существует простое описание v' как каррирования внутреннего продукта по первому параметру и V* как набора функций, которые являются однопараметрическими. карри с разными первыми векторами.

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

Кроме того, я изначально думал, что следует запретить индексирование в ковектор. Однако индексирование v'[1] эквивалентно применению v' к каноническому базисному вектору e₁ = (1, 0, ...) , так что v'[1] можно определить как <v, e₁> . Более общая семантика индексации, такая как 1:n естественно вытекает из применения v' к подходящей матрице проекции, где каждый столбец является каноническим базисным вектором.

Рассмотрение семантики индексирования ковекторов в контексте # 987 дает еще одну причину, по которой ковекторы не являются AbstractVector s: в целом невозможно индексировать v' дешево, потому что все зависит от того, насколько дорогие количества например, <v, e₁> предназначены для вычисления. В общем, у вас может быть внутренний продукт, определенный относительно матрицы <v, w> = v'*A*w а стоимость индексации во многом определяется продуктом matvec A*w (или A'*v ), что, безусловно, слишком дорого, чтобы квалифицироваться как AbstractVector .

Раз уж мы говорим о DFT и нормах ... это пришло мне в голову сегодня

если f является функцией вектора и производит скаляр, тогда я бы хотел, чтобы diff(f) была функцией, которая принимает Vector и производит Covector так что f(x) ~= f(x0) + diff(f)(x0) * (x-x0) . Очень часто градиент используется для получения линейной аппроксимации постепенного изменения выходных данных функции с учетом постепенного изменения входных данных. Если вход является векторным, то кажется естественным, что градиент сжимается по всем измерениям входа.

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

Мое чутье говорит, что «градиент функции векторного ввода в точке является ковектором» более важен.

Я подозреваю, что сейчас правильнее всего сделать в Base только определить:

ковектор умножить на другой вектор
ковектор, умноженный на матрицу
covector ', чтобы получить вектор

Несмотря на впечатление, которое вы могли получить от моих предыдущих ужасных постов, +1 к этому.

Тем не менее, несколько замечаний по этому поводу:

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

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

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

Точнее:

Пусть V = V (F) и W = W (F) вектор над некоторым полем элементов F,
v и w - векторы, которые являются элементами V и W соответственно,
<.,.> - такое скалярное произведение, что <.,.>: V × W → F и v, w ↦
Довольно странно и я считаю невозможным иметь внутренний продукт, определенный между двумя разными векторными пространствами V и W. Как вы определяете свойство положительной определенности?
Ковектором, отвечающим v, является линейный функционал v ': W → F, выполняющий отображение w ↦.

Обычное описание заканчивается утверждением, что v 'является элементом дуального пространства V *, и о том, что такое дуальное пространство, больше не говорится.

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

Рассмотрение семантики индексации ковекторов в контексте # 987 дает еще одну причину, по которой ковекторы не являются абстрактными векторами: в целом невозможно дешево индексировать v ', потому что все зависит от того, насколько дорогие величины, такие как= v'_A_w, и в стоимости индексации преобладает продукт matvec A_w (или A'_v), который, безусловно, слишком дорог, чтобы квалифицировать его как AbstractVector.

Это своего рода аргумент цикла. Как упоминалось выше, линейный функционал (ковектор) является более общим и существует даже для векторных пространств без внутреннего произведения. Во-вторых, когда метрика является положительно определенной матрицей A, естественным отображением вектора v в ковектор действительно является v'_A. Однако что это за v 'здесь? Это уже отображение из v в другой ковектор, определенный относительно стандартной евклидовой нормы (с тождеством в качестве метрики)? Нет. Если векторы имеют контравариантные (верхние) индексы, а ковекторы имеют ковариантные (нижние) индексы, то метрика A имеет два нижних индекса и внутреннее произведение равно= v ^ i A_ {i, j} v ^ j. Таким образом, это отображение можно записать как (с phi ковектором, связанным с вектором v) как phi_j = v ^ i A_ {i, j}. (Обратите внимание, это отличается от типичного линейного оператора, который имеет вид w ^ i = O ^ i_j v ^ j). Следовательно, v в этом выражении v'_A по-прежнему имеет верхний индекс, это все тот же вектор.

На самом деле, один из моментов, который я пытался подчеркнуть выше, заключается в том, что люди часто пишут v ', когда они на самом деле не пытаются работать с ковектором. Они просто пытаются писать выражения, определенные на векторах, например внутренний продуктили что-то вроде v ^ i A_ {i, j} w ^ j в знакомом матричном представлении как v'_A_w. Даже стандартный евклидов скалярный продукт v'w следует читать как v ^ i delta_ {i, j} w ^ j и не требует введения конвекторов. Как упоминалось выше, я думаю, что использование настоящих правильных ковекторов, которые не являются какой-то скрытой формой скалярных произведений, довольно ограничено в большинстве приложений. Люди просто пишут v ', чтобы иметь возможность использовать удобный матричный синтаксис, но на самом деле они вычисляют внутренние продукты и т. Д., Которые определены относительно векторов, а не ковекторов.

С другой стороны, раньше было несколько замечаний об ассоциативности умножения, если бы мы имели v '== v, как в первоначальном предложении Стефана. Однако даже без этого я бы не сказал, что * ассоциативно, даже если v '- ковектор, который не отождествляется с обычными векторами:
A_ (v'_w) - матрица
(A_v ') _ w выдает ошибку

Градиент скалярнозначной функции действительно является одним из подходящих приложений ковекторов, т.е. без аргументов градиент является ковектором. В таких приложениях, как сопряженный градиент, неявно предполагается, что существует метрика (и фактически обратная метрика) для отображения этих ковекторов обратно в векторы. В противном случае нет смысла делать что-то вроде x ^ i (вектор положения) + alpha g_i (градиент). Правильный способ записать это - x ^ i + alpha delta ^ {i, j} g_j, где delta ^ {i, j} - обратная метрика. Если кто-то пытается определить методы оптимизации на многообразиях с нетривиальной метрикой, то необходимо принять во внимание эту (обратную) метрику. Об этом есть хорошая книга:
http://sites.uclouvain.be/absil/amsbook/
и соответствующий пакет Matlab:
http://www.manopt.org

22 октября 2014 года в 22:52 goretkin [email protected] написал:

Раз уж мы говорим о DFT и нормах ... это пришло мне в голову сегодня

если fis является функцией вектора и производит скаляр, тогда я бы хотел, чтобы diff (f) была функцией, которая принимает вектор и создает Covector, так что f (x) ~ = f (x0) + diff (f) ( х0) * (х-х0). Очень часто градиент используется для получения линейной аппроксимации постепенного изменения выходных данных функции с учетом постепенного изменения входных данных. Если вход является вектором, то кажется естественным, что градиент является ковектором.

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

Мое чутье говорит, что градиент - это ковектор, более важный.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub.

A_ (v'_w) - матрица
(A_v ') _ w выдает ошибку

Вот дерьмо.

b2e4d59001f67400bbcc46e15be2bbc001f07bfe05c7c60a2f473b8dae6dd78a

Эта ветка просрочена для публикации с юмористическим изображением в ней.

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

Циркулярный - действительно неправильный термин. В этом абзаце я пытался сказать, что я могу перевернуть эту линию рассуждений относительно эффективной оценки записей, поскольку, например, в случае (сопряженного) градиента на изогнутых многообразиях, я предполагаю, что градиент (ковектор) g_i можно вычислить эффективно, но соответствующий вектор A ^ {i, j} g_ {j}, который будет добавлен к x ^ i (с A ^ {i, j} в качестве обратной метрики), может оказаться неэффективным. Я только что заметил, как плохо изложено мое предыдущее сообщение, появившееся на Github; это было нормально, когда я написал электронное письмо. Пытался исправить сейчас. Я не хочу повторяться и создавать (неправильное) впечатление, будто я не согласен с текущими предложениями. Единственное, что я пытался сделать.

  1. Двойственное пространство, безусловно, является векторным пространством, поэтому его элементы являются векторами. Однако цель не должна заключаться в том, чтобы все объекты, которые имеют математическое значение вектора, были подтипом AbstractVector Джулии, а также не все объекты, которые являются подтипами AbstractVector имели надлежащие математические характеристики. вектора. В этом смысле мне нравится имя Matrix гораздо больше, чем имя Vector , поскольку оно имеет гораздо меньшее значение. Следовательно, я думаю, я могу согласиться с тем фактом, что какой бы тип Covector не вводился как часть этого предложения, он не должен быть подтипом AbstractVector или даже AbstractArray , даже в тех случаях, когда V * естественно изоморфно V (декартово пространство, которое составляет, вероятно, половину приложений).
  2. Внутренний продукт позволяет вам построить отображение от V к V_, но это не является предпосылкой для существования V_. Конечно, это звучит как неуместная проблема, поскольку в программировании всегда есть конечномерное векторное пространство и всегда есть внутренний продукт (даже если он может не иметь отношения к приложению), но практический момент, который я пытался сделать это. В программировании есть подходящие применения ковекторов, например хороший пример градиента от @goretkin , но в большинстве приложений, где люди пишут v' , они на самом деле не пытаются построить ковектор, они просто пытаются написать билинейный отображение между двумя векторами (т.е. V x V в скаляр), как в v'_A_w = v ^ i A_ {i, j} w ^ j или даже v'w = v ^ i delta_ {i, j} w ^ j, используя удобное матричное представление. Я предпочитаю явно писать dot(v,A*w) , но это личный выбор. Поскольку у нас нет информации о верхних или нижних индексах матриц, можно построить варианты использования, где в скалярном выражении, таком как v'*A*w оба v' и w могут быть векторами или ковекторами. в математическом смысле. Единственным следствием этого является то, что я не уверен, действительно ли я доволен вызовом типа v' Covector , поскольку, как и имя Vector нем слишком много математических вычислений. коннотации, которая может быть неоправданной в большинстве приложений, что затем приводит к большему количеству (в основном неуместных) дискуссий, подобных этому.

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

Я все больше поражался тому, насколько внутренне последовательным
а самодостаточным является мир тензоров ранга 2 с одним верхним и одним нижним индексами.
Это то, что мы условно называем матричными вычислениями или просто старой простой линейной алгеброй.
Черт возьми, пока я могу найти множество примеров, таких как norm (v, p) или fft (v), где функции
различаются, являются ли они векторами или ковекторами, у меня действительно нет ни одного хорошего примера (пока!)
функции, которая естественно различается для вектора и матрицы с одним столбцом.
(Кто-нибудь, помогите мне, наверняка должен быть один, даже много !!)

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

В результате разговора с @jiahao меня действительно поразило, что есть два совершенно особенных мира:
Линейная алгебра (массивы с одним противником и одним со) и есть простые тензоры
(все - ко, без контрас). Последний хорошо работает в APL an Mathematica.
Первый относится ко всей линейной алгебре и был лучше всего зафиксирован в MATLAB раньше.
они привиты на массивы размерности больше 2. Имеется более полная общность
что после слов с @JeffBezanson показалось, что это не так уж и безумно.

кстати, я думаю, что на это ушло около года, если не больше, но мы, наконец, серьезно относимся к этому :-)

Что касается
A_ (v'_w) - матрица
(A_v ') _ w выдает ошибку

Только матричное умножение "*" ассоциативно. Перегруженная матрица скалярных времен никогда не была
ассоциативный. Ни даже в математике, ни даже в MATLAB.

A_ (v'_w) - матрица
(A_v ') _ w выдает ошибку

Хороший улов. Но есть ли случаи, когда отсутствие ошибок дает разные ответы? Связанный с этим вопрос: что должен делать скалярный * ковектор?

Матрица перегруженных скалярных времен никогда не была ассоциативной. Ни даже в математике, ни даже в MATLAB.

Операции с участием только матриц и скаляров ассоциативны (см. Пункт 3). Поскольку мы можем рассматривать векторы как массивы из 1 столбца, их включение также не вызывает проблем. Проблема здесь в том, что Covector - это оператор, который может уменьшать размеры (преобразовывать векторы в скаляры), поэтому я не уверен, как это подходит.

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

(A+s)*v != A*v + s*v

Это очень хорошее резюме:

В результате разговора с @jiahao меня действительно поразило, что есть два совершенно особенных мира:
Линейная алгебра (массивы с одним противником и одним со) и есть простые тензоры
(все - ко, без контрас).

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

В первом случае все векторы v являются столбцами, все v' являются ковекторами, а все матрицы являются линейными операторами V-> W (например, они живут в W ⊗ V_). Это исключает представление, например, билинейного отображает V x V -> скаляр (например, v ^ i A_ {i, j} w ^ j), поскольку для этого требуется матрица с двумя нижними индексами (например, проживание в V_ ⊗ W_). Конечно, вы все еще можете использовать его на практике и записать как v'_A*w , но это как бы конфликтует с выбранной номенклатурой объектов. Кроме того, он не указывает, в каком пространстве живут массивы более высокого порядка.

Последний случай решает эту проблему, поскольку имеет (V == V *), но приводит к неожиданным результатам в виде v' == v . Кроме того, это решение фактически ограничено вещественными векторными пространствами, поскольку даже в комплексных пространствах со стандартным евклидовым внутренним произведением (т.е. «комплексное декартово пространство») двойственное пространство естественно не изоморфно V, но конъюнктивно (V) (V bar), сопряженное векторное пространство (см. гильбертовы пространства в http://en.wikipedia.org/wiki/Complex_conjugate_vector_space)

Что касается неассоциативности, то в этом отношении текущее поведение, когда v'*v не создает скаляр, а представляет собой массив, на самом деле более согласовано, поскольку тогда и A*(v'*v) и (A*v')*v выдает ошибку.

"Линейная алгебра" может быть дополнительно разделена на различные варианты представления векторов и ковекторов:

  • Предложение Covector, также известное как «забавный вектор-строка», мы обсуждали недавно.
  • Семантика «нет истинных векторов», которая объединяет (N-векторы как Nx1-матрицы), (N-векторы-строки как 1xN-матрицы), которая одобрена Matlab и т.п.
  • Текущий способ Джулии - не объединять плотные N-векторы и Nx1-матрицы, объединять разреженные N-векторы и Nx1-матрицы, представлять N-строки-векторы как 1xN-матрицы.

Интересно, строго ли необходимо также объединять (скаляры, 1-векторы и 1x1-матрицы) в мире «нет истинных векторов»; @alanedelman и я обсуждали это вчера, и в числовой линейной алгебре коммутативность векторных * скалярных и скалярных * векторов используется повсюду, но скалярное произведение вектор * - это то, что не волнует, выполняет ли он (N,) * ( ,) или (N, 1) * (1,1).

1) В конце концов, самые важные вещи - это А) простота использования и Б) производительность.
2) Два мира должны уметь сосуществовать в полной гармонии. При выполнении инженерных работ я считаю, что нет ничего более интуитивно понятного и простого, чем тензорная запись. Одно из предложений, если можно, могло бы состоять в том, чтобы включить флаг среды или пакет мог быть импортирован в начале программы. Это обеспечит простоту использования и простую логику программирования. Разве это невозможно, или мы должны выбрать одну общую схему?

Вроде есть два варианта
1) разрешить импорт набора инструментов, патча или флага среды для одновременной работы
2) создать язык, способный объединить особые миры, но при этом быть простым в использовании и быстрым

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

Обращаю ваше внимание на:

Исследовательская группа по геометрической алгебре
http://www.mrao.cam.ac.uk/~clifford/pages/introduction.htm

Курс лекций по геометрической алгебре
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/course99/

Физические приложения геометрической алгебры
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/

Единый математический язык для физики и инженерии в 21 веке
http://www.mrao.cam.ac.uk/%7Eclifford/publications/ps/dll_millen.pdf

Геометрическая алгебра
http://arxiv.org/pdf/1205.5935v1.pdf

Основы вычислений геометрической алгебры
http://link.springer.com/book/10.1007%2F978-3-642-31794-1

Геометрическая алгебра.
http://link.springer.com/book/10.1007%2F978-1-84996-108-0

Если посмотреть на это немного подробнее, это кажется действительно потрясающим.

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

  • Жозеф Луи Лагранж

Представьте, что вам нужны очки, но вы не знаете, что вам нужны очки. Затем, когда вы получаете очки, мир неожиданно меняется. GA - это как очки для внутренней части вашего мозга.

  • Пабло Колапинто

Кажется, у этого парня правильная идея ... https://github.com/wolftype/versor

Типичные библиотеки матричных операций содержат встроенные встроенные функции для умножения векторов и матриц. Геометрическая алгебра объединяет многие другие математические методы (матричные, тензорные, векторные и алгебры Ли). Versor аналогичен, но на стероидах, где векторы и разреженные матрицы различных размеров просто называются многовекторами и представляют геометрические элементы, выходящие за рамки только направлений xyz и матриц преобразования. Круги, линии, сферы, плоскости, точки - все это алгебраические элементы, как и операторы, которые вращают, скручивают, расширяют и изгибают эти переменные. И эти элементы, и операторы являются мультивекторами, которые умножаются множеством различных способов.

В этом видео подробно рассказывается о Versor, математическом языке программирования GA.
https://www.youtube.com/watch?v=W4p-e-g37tg

Вот слайды для этого. https://github.com/boostcon/cppnow_presentations_2014/blob/master/files/generic_spaces.pdf

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

@ esd100 , я думаю, было бы полезно продолжить обсуждение этой

@johnmyleswhite Я искал термин "тензор", и он был упомянут 171 раз (сейчас 172 раза). Хотя я согласен с вашим утверждением, в целом в этой ветке 157 комментариев (сейчас 158), некоторые из которых прямо или косвенно касаются заголовка исходного сообщения (серьезное отношение к переносу векторных изображений). Я думаю, что мой пост косвенно связан с названием исходного поста, предлагая более широкую перспективу того, что можно сделать с новыми приложениями тензорной математики с помощью геометрической алгебры. Я считаю, что создание в Джулии той многомерной силы, которую имеет Версор, было бы дополнительной полезной особенностью Джулии. Видео на YouTube озаглавлено «Общее программирование общих пространств: геометрическая алгебра во время компиляции с помощью C ++ 11». Я не понимаю, почему это не могла быть Джулия вместо C ++. Опять же, я не математик или компьютерный ученый.

@ esd100 Геометрические алгебры интересны, но это не самая общая тема, которую охватывает этот выпуск. Геометрическая алгебра, которая нас будет интересовать в первую очередь, - это вещественная алгебра Клиффорда Cl (R ^ n, I); однако нас также будут интересовать другие алгебры Клиффорда, которые _не_ геометрические алгебры, такие как алгебры Клиффорда над комплексными векторами Cl (C ^ n, I) или даже алгебры над произвольными полями или некоммутативными кольцами Cl (F ^ n, I) . Кроме того, мы даже не хотим обязательно ограничиваться алгебрами Клиффорда, но хотим рассмотреть, как линейная алгебра обобщается на более общую систему тензорной алгебры, где квадратичные формы (скалярные произведения) не обязательно определены.

С точки зрения тензорной алгебры, предложение « v' - это запретная операция » в OP имеет смысл, поскольку оно последовательно обобщается биалгебраические расширения, которые приводят к тензорным коалгебрам, где предложение "v 'порождает ковектор" является очень естественным. Однако я не уверен, как предложение «нет истинного вектора» обобщается в тензорную алгебру.

@jiahao Спасибо за очень информативный ответ. Приятно, когда кто-то, владеющий предметом, проливает свет. Мне было бы интересно подробнее изучить эти темы, чтобы лучше понять. Мне любопытно, почему существует супероптимизированный код для BLAS, а супероптимизированный код для более сложных алгебр, таких как Алгебра Клиффорда, нет.

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

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

Вопрос в том, как распространить эти «голые» массивы на математические понятия линейной алгебры? Как сделать так, чтобы синтаксис выглядел как обычная математика? Будет ли людям комфортно, что v ''! = V? И т. Д. И т. Д.

Во-первых, мы не хотим делать массивы хуже, просто чтобы заниматься линейной алгеброй. Мы не хотим добавлять дополнительные данные в вектор или ненужные аргументы шаблона в массив. Мы хотим быть такими же быстрыми, как C / C ++, когда не занимаемся линейной алгеброй (и, надеюсь, такими же быстрыми, как C / fortran, когда мы).

Второе, что мы все должны понять, это то, что полное многомерное сжатие тензора требует значительного количества дополнительной информации. Для двухмерных тензоров напишите любое умножение как A_B'_C * D ... в то время как для общих тензоров более естественно думать о графике для определения сжатия (диаграмма тензорной сети или факторный граф - то же самое под разными именами). Для тензоров большой размерности имеет смысл обернуть голые массивы и украсить их векторными пространствами и т.д., чтобы отслеживать все. Это можно сделать в таких пакетах, как те, над которыми работает Jutho (и, возможно, другие). Нет смысла рассматривать значение «трёхмерных тензоров» - никого не будет интересовать использование этой функции при наличии permutedims или при использовании специального тензорного пакета.

Основным пользователям потребуется умножать матрицы, складывать векторы и т. Д. Они захотят переставить матрицы. Им также понадобится внутренний продукт и внешний продукт. Нравится вам это или нет, но они определяются в сочетании с двойным пространством. Мы знаем, что это за действительные числа и комплексные числа, и заранее их определили. И да, это правда, что двойное пространство реального конечного векторного пространства кажется в основном одним и тем же, и я считаю, что именно это затуманивает всю проблему. Самая большая текущая проблема заключается в том, что у нас нет правильного двойного вектора. Без него мы не сможем «писать» уравнения в Julia, как на бумаге и ручке - огромная проблема! Что еще более важно, у нас нет v '' = v. Это очень неинтуитивно.

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

Меня не беспокоит, что мы усложняем систему типов. Я считаю, что это необходимо для инкапсуляции математики в том виде, в каком ее изучали в средней школе и университете: иметь ассоциативный * (где четко определено), иметь v '' = v, иметь как «бюстгальтеры», так и «кеты» (я Я использую здесь обозначения Дирака линейной алгебры) и т. д.

Кстати, если это было реализовано в Julia 0.4, я не обращаю внимания! Я все еще работаю над пониманием 0.3.4 ...

@andyferris , в целом я со всем согласен. Я думаю, что мое предложение здесь можно было бы немного изменить и работать хорошо: вместо того, чтобы M*v' было ошибкой, пусть он создает ковектор, а вместо v*M является ошибкой, пусть он создает вектор. Для меня это концептуально означает, что M не зависит от того, увеличиваются или уменьшаются его размеры - вы можете написать v'*M*w или v*M*w' для внутреннего продукта, и любой из них будет работать.

Одна мысль, которая ходила вокруг, заключается в том, что массивы row-major и col-major и M.' просто меняются с одного на другой, и аналогично v' будет основным вариантом строки для v - имеет ту же форму, но концептуально хранится в другом порядке. Хотя не уверен в этом.

+1 к @andyferris . Я также думаю, что нам просто нужны простые типы оболочки Transpose и ConjTranspose которые позволяют нам использовать краткий синтаксис матричной алгебры для записи произведений векторов, матриц и их транспонирования. Что касается пункта 2 предложения @StefanKarpinski , я бы не ограничивал эти оболочки контейнером объектов AbstractArray и не делал бы типы частью самой иерархии типов AbstractArray . Transpose(x) должен быть просто типом, который получается в результате написания x' в выражении и позволяет отложить его вычисление / отложить вычисление, в зависимости от остальной части выражения, отправив Transpose для остальных операций в выражении (вероятно, это будет оператор умножения * в 99,9% случаев). Однако люди должны иметь возможность придавать этому синтаксису значение для новых типов, которые могут не быть частью иерархии AbstractArray , таких как объекты матричной факторизации или другие типы для определения, например, линейных операторов.

Что касается конкретного случая матриц и векторов, я действительно не вижу варианта использования для M*v' , но я внутренне не против этого. Важно то, что он оправдывает основные ожидания (т.е. правила 2, 4, 5, 6 и 8 в конце предложения Стефана).

Главный заключительный момент обсуждения - это, вероятно, взаимодействие с нарезкой. Должен ли сегмент строки матрицы автоматически быть Transpose ? Мой голос здесь за правила нарезки APL, где это не так.

@Jutho , мотивация состоит в том, чтобы сделать * ассоциативными - A*(v'w) и (A*v')*w оба работают и дают одинаковые результаты по модулю неассоциативности базового типа элемента (например, с плавающей точкой). точка).

Чтобы (A*v')*w давал тот же результат, что и A*(v'*w) , A*v' должен быть массивом N=3 . Вы это имели в виду? Я полностью упустил это.

Хорошо, очень интересно, у меня есть несколько комментариев.

Сначала нам нужно обратиться к основным пользователям. По сути, его основная функция - в первую очередь использование Array{T,n} качестве n -мерного хранилища. Это имеет смысл для программистов , которые не имеют ни малейшего представления о линейной алгебре , но которые хотят манипулировать данные в Джулию. Из-за этого ни при каких обстоятельствах срез массива не может возвращать ковектор! Это концепция строго линейной алгебры, которая применяется только к определенным типам данных T которые могут определять векторное пространство и его двойственное (например, числовые данные или «поля»).

Я мог пойти любым путем с нарезкой APL. Это кажется достаточно интуитивным. Он стабилен по типу и не делает ничего удивительного. Хотя я не считаю необходимым вносить это изменение, чтобы сделать линейную алгебру самосогласованной ... это могло бы быть хорошо (хотя это может быть критическое изменение). Меня тревожит, что M[1,:] имеет размер 1xn, а M[:,1] - размер n (а не nx1) ... это кажется слишком асимметричным ... но я думаю, это хорошее напоминание о том, как обстоят дела выложил в память. В любом случае ... это изменение следует вносить только в том случае, если оно имеет смысл для хранения и обработки абстрактных данных. Для меня это изменение ортогонально обсуждению линейной алгебры.

Итак, даже если мы не реализуем правила нарезки APL, мы все равно можем спасти осмысленную линейную алгебру довольно просто. Если вы хотите i й вектора - столбец M называют colvec(M,i) , и если вы хотите i - й строка совместного вектора M вызова rowvec(M,i) . Если i был кортежем или вектором, он мог бы возвращать кортеж, вектор или (со) векторы (может ли это быть полезно в некоторых случаях для рассуждений о распараллеливании?). Если вы предпочитаете матрицу, просто используйте обычную нотацию индексации. Если мы используем правила нарезки APL, то же самое очень полезно для различения действий срезов строк и столбцов с помощью символа * . (Необходимо учитывать, как сложное сопряжение ведет себя с rowvec ).

Таким образом, если вы занимаетесь линейной алгеброй, все будет иметь смысл, и пользователю не придется беспокоиться о том, какое конкретное правило нарезки реализует Джулия. Оператор ' будет воздействовать только на векторы и ковекторы (чтобы изменить оформление) и на матрицы (либо прямым, либо ленивым способом). Возможно, будет проще вспомнить, как извлечь базисные векторы из собственного разложения, о котором я всегда забываю.

Для * , я думаю, что матричная / векторная алгебра в Julia должна работать так, чтобы она выглядела и ощущалась как математика как оператор умножения, а не как оператор тензорного произведения между двумя векторами, двумя ковекторами, двумя матрицами и т. Д. не должен допускать M*v' , потому что строго в математике вам нужно писать уравнение с помощью символа «\ otimes» (знак раз в кружке), а не стандартного символа умножения. С помощью символа умножения это значение не определено! У нас не может быть w*M*v' == v'*M*w потому что умножение матриц не коммутативно. Опять же, для M*v'*v нет смысла говорить об ассоциативности * . Вы можете интерпретировать это как innerproduct(outerproduct(M,v'),v) что требует двух разных символов умножения, или как скалярное умножение M * innerproduct(v',v) где _действительно_ имеет смысл использовать * для обоих. С этим выражением нам может потребоваться заключение в скобки, или, возможно, компилятор мог бы искать значимый порядок операций (обратите внимание, что самый быстрый порядок оценки также является тем, что я считаю единственно допустимым порядком оценки).

С векторами, ковекторами и матрицами мы можем получить алгебраическую систему, совместимую со стандартной математикой. Всякий раз, когда вы хотите выполнить внешнее произведение между двумя векторами, двумя ковекторами или двумя матрицами, вы по своей сути переходите к полилинейной алгебре или общей тензорной алгебре. Здесь у вас могут быть матрицы и векторы, которые умножаются как kron(M,N) с использованием нового символа. Или вы можете потребовать от пользователей использовать полный пакет мультилинейной алгебры. Или что-то в этом роде ... кажется, выходит за рамки исходного вопроса (который был 14 месяцев назад, кстати ...)

Подводя итог, я вижу здесь четыре почти совершенно разных вещи:

  1. Улучшите нарезку Array произвольных данных с помощью правил нарезки APL.
  2. Сделайте линейную алгебру в Julia более интуитивной и полностью совместимой с математикой, добавив несколько простых понятий: ковекторы и метод извлечения векторов и ковекторов из матриц, а также операции между ковекторами, матрицами и т. Д. Многие пользователи могут даже не заметить ковекторы, поскольку использование матриц 1xn и nx1 будет продолжать работать, как ожидалось, и вектор будет вести себя так же.
  3. Для скорости реализуйте ленивую оценку для transpose , conj и т. Д., Используя типы упаковки, похожие на ковектор, но также и для матриц.
  4. Разработайте тензорный пакет общего назначения и, возможно, добавьте его в стандартную библиотеку.

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

Да, извините, мое предложение разрешить M*v' самом деле не имеет никакого смысла, поскольку разные способы связывания продуктов дают совершенно разные формы. Итак, я думаю, что если мы собираемся изменить это, мое первоначальное предложение будет правильным. Пока что это лучшая комбинация с поведением нарезки APL. Конечно, хотим ли мы срезать APL или нет - это вопрос более высокого уровня.

Я просто хотел сказать несколько вещей и, пожалуйста, отнеситесь к ним с недоверием. Это просто мнение, и я знаю, что мое не имеет особого значения. Однако, когда я читал комментарий @andyferris, меня поразила одна вещь. Не думаю, что я согласен с комментарием о том, что он должен иметь смысл для программистов, которые не имеют представления о линейной алгебре, но хотят манипулировать данными в Julia. Я не думаю, что программисты - это та аудитория, для которой следует писать математические программы. Если программист хочет манипулировать данными, существует множество языков, которые позволят ему это сделать. Julia - это язык для вычислений, и он должен позволять сложные технические вычисления.

@ esd100 Правда, я об этом подумал. Но с Джулией я думал, что мы хотим быть _жадными _... хотим удовлетворить многие цели и преуспеть во многих вещах. Для манипулирования нечисловыми данными могут быть подлинные научные причины. Могут быть действующие или бывшие ученые, которые являются пользователями Julia и хотят заниматься более общим программированием. Ранее я говорил, что мы не должны делать Array медленнее или занимать больше памяти, давая ему дополнительное поле, чтобы говорить о транспонировании / спряжении.

Если выполняется нарезка APL, то фрагменты строки или столбца должны возвращать один и тот же объект. Возник вопрос: что лучше всего вернуть M[1,:] без нарезки APL? Что ж, если M[1,:,:,:,:] возвращает Array{T,n} , я думаю, что всех запутает, если M[1,:] M[1,:,:,:,:] вернет covector{T} . Что, если мы позволим M = zeros(3,3,3) и вызовем M[1,:] , который в настоящее время возвращает матрицу 1x9? Должны ли мы сделать это тоже ковектором? Что, если бы M было Array{String,3} ?

Мне это показалось бы довольно удивительным и запутанным. Это также не согласуется с изменениями нарезки APL, если это произойдет в будущем. Таким образом, я предлагал добавить новые функции для целей линейной алгебры, rowvec(M,i) и colvec(M,i) . Он не идеален, он добавляет новые функции ... но, по крайней мере, ясно, что они должны возвращать для целей линейной алгебры. Это единственное, о чем я мог подумать, у которого были хорошие свойства для универсальных массивов и хорошие свойства для линейной алгебры (наряду с типом ковектора), при этом я не пытался удовлетворить многолинейную алгебру. Было бы неплохо, если бы у кого-нибудь есть более удобная нотация для извлечения ковекторов из матриц!

@ esd100 : Я почти уверен, что именно программисты созданы для Юлии. Научные вычисления - сильная сторона Джулиаса, но многие пользователи Джулии используют их просто как язык программирования общего назначения.

Я согласен с @andyferris, что нужно отделить требования к тензору от требований контейнера.

Не прочитав всю цепочку, моя личная проблема заключается в том, что если у меня есть 3D-массив A (например, томографические данные) и я делаю

imagesc( data[1,:,:] )

это не работает. IMHO data[1,:,:] , data[:,1,:] и data[:,:,1] должны быть 2D-массивами (или подмассивами). В настоящее время я использую самоопределяемую функцию squeeze для решения этой проблемы.

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

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

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

@jutho Я чувствую, что вы правы, но мне интересно, на чем основывается ваш вывод о том, что пакеты «лучше» и по сравнению с какими альтернативами?

Это очень просто. Функциональность, которая важна для большинства пользователей Julia, принадлежит Base. Более особенная функциональность принадлежит пакету.

Конечно, провести черту здесь непросто. Но лучший способ внести что-то в базу - это создать пакет, чтобы многие люди могли это протестировать. Различные основные функции, реализованные в Base, сначала были разработаны в пакете.

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

Что касается текущего обсуждения, каждый, кто работает с векторами и матрицами, будет ожидать возможности транспонировать вектор и иметь v ^ T * w = scalar. Но эти векторы и матрицы могут использоваться для представления ряда различных вещей, и это, вероятно, будет зависеть от области / дисциплины. В сообщениях выше я приводил примеры того, где v ^ T может не быть действительным ковектором.

11 января 2015 года в 18:10 esd100 [email protected] написал:

@jutho https://github.com/jutho Мне кажется, что вы правы, но мне интересно, на чем основывается ваш вывод о том, что пакеты «лучше» и по сравнению с какими альтернативами?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -69501771.

Это очень просто. Функциональность, которая важна для большинства пользователей Julia, принадлежит Base. Более особенная функциональность принадлежит пакету.

Не думаю, что все так просто. Например, в Base есть функции Бесселя, и маловероятно, что они будут важны для большинства пользователей Julia. Причина, по которой они могут быть в Base, заключается в том, что существует один достаточно универсальный стандарт для функций Бесселя, и никто, вероятно, не будет использовать эти имена для чего-то другого или ожидать, что они сделают что-то другое.

Благодаря осторожным (некоторые могут сказать, многословным) соглашениям об именах, Mathematica может поместить более 4000 функций в основной язык, и я считаю чрезвычайно удобным почти никогда не загружать пакет. Напротив, когда я использую Python / Sage, нет ничего необычного в том, что один файл / записная книжка явно импортирует 200 функций вверху (избегая более неотслеживаемого синтаксиса «from ___ import *»). Каждый раз, когда мне нужно использовать функцию, которая не является встроенной, мне приходится выполнять несколько дополнительных шагов:

(1) Попытайтесь вспомнить, использовал ли я его уже в этом файле, и / или выполните текстовый поиск для подтверждения (ограничиваясь целыми словами, если имя является подстрокой чего-то еще)
(2) Либо: (а) сразу же добавить смысл, потеряв ход мыслей и найдя его снова; или (б) попробуйте не забыть добавить импорт после того, как я сделал то, что делал, сохраняя в памяти что-то еще
(3) Для менее распространенных функций, возможно, придется искать, в каком пакете они находятся.

Это может раздражать и изнурять. Поэтому я думаю, что, как и функции Бесселя, все, что считается стандартным - и, следовательно, не вызовет конфликтов имен или путаницы - должно быть в Base, независимо от того, использует ли это много людей. Конечно линейная алгебра.


Возвращаясь к теме, мы говорили о нескольких уровнях функциональности - переходе от массивов как семантически голых контейнеров (как реализовано в Base.AbstractArray ) к декартовым тензорным объектам с семантикой вверх / вниз (как описано моим Предложение AbstractTensorArray для более общих тензорных объектов с индексами, отображенными в векторные пространства (как в TensorToolbox @Jutho ) --- увеличения общности и уменьшения потенциала оптимизации.

Я думаю, что для пользователей важно иметь возможность легко переходить между этими уровнями, не в последнюю очередь потому, что пользователи _могут не знать_, какой уровень общности им действительно нужен, когда они начинают. В качестве простого примера @Jutho указал, что в примере обработки изображений AbstractTensorArray s до одиночных декартовых векторных пространств по умолчанию и соответственно пробелы "последовательности" --- тогда это становится почти бесшовным. Пользователи нижнего уровня функциональности не должны знать или заботиться о верхних уровнях, пока они им не понадобятся.

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

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

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

Спасибо, Тони, я согласен. Кроме того, с нашей системой пакетов действительно не проблема иметь «набор инструментов», как пакеты, которые группируют несколько вместе. С макросом @reexport это хорошо работает.

Но есть еще один момент. В пакете намного проще продвигать идею. Если кто-то хочет что-то изменить, он просто делает это. Внутри Базы все гораздо более ограничено.

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

Какой в ​​настоящее время самый идиоматичный способ создания векторной * матрицы?
Например, у меня есть X с формой (K, N) и b с формой (K,) , и я хочу умножить на b на осталось, чтобы получить вектор длины (N,) .
Я называю BLAS.gemv('T',1.0,X,b)
Или reshape(b'*X,size(x,2))
Оба немного некрасивы.

Я думаю, ты мог бы сделать X'b

2015-03-13 17:15 GMT-04: 00 joschu [email protected] :

Какой в ​​настоящее время самый идиоматичный способ создания векторной * матрицы?
Например, у меня есть X с формой (K, N) и b с формой (K,), и я хочу
умножить на b слева, чтобы получить вектор длины (N,).
Могу ли я вызвать BLAS.gemv ('T', 1.0, X, b)
Или изменить форму (b '* X, размер (x, 2))
Оба немного некрасивы.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79405868.

Да, я думал, это запустит копию X.
Но @time, похоже, указывает, что копии нет, на основе распределения памяти

julia> X = rand(1000,1000); y = rand(1000);

julia> <strong i="8">@time</strong> y'X;
elapsed time: 0.00177384 seconds (15 kB allocated)

julia> <strong i="9">@time</strong> X'y;
elapsed time: 0.000528808 seconds (7 kB allocated)

Мы проводим необычный синтаксический анализ, поэтому X'y заканчивается как Ac_mul_B(X,y) и делает
тот же звонок BLAS, который вы предложили.

2015-03-13 17:28 GMT-04: 00 joschu [email protected] :

Да, хотя я думал, что это вызовет копию X.
(Но @time https://github.com/time, похоже, указывает, что нет
копировать, в зависимости от распределения памяти

Юлия> X = ранд (1000,1000); у = ранд (1000);

Юлия >
затраченное время: 0,00177384 секунды (выделено 15 КБ)

Юлия >
затраченное время: 0,000528808 секунд (выделено 7 КБ)

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79421713.

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

И последнее: кто-нибудь думал об этом ». может на самом деле быть совершенно другим животным, чем '? Последний имеет правильную структуру, чтобы быть дуальным, но ». не работает, если базовое поле не является действительным числом. Это безумие назначать? понятие "перевернуть все индексы" транспонировать и оставить все понятия двойственности за ', что дает "смешной вектор-строку" / эрмитово сопряжение?

Я думаю, что в любом случае conj(transpose(x)) должно быть эквивалентно ctranspose(x) .

Как утверждалось выше, я надеюсь, что ни одному из типов оболочки, которые создадут transpose и ctranspose , будет присвоено имя, содержащее конкретную математическую концепцию, например dual . Научный язык, такой как Julia, должен предоставлять набор полезных структур данных и должен реализовывать общие операции, но я думаю, было бы ошибкой связывать с ним строгую математическую структуру, так как это не подходит для некоторых приложений и слишком сложно для другие. Пользователь должен использовать эти структуры данных и операции для реализации математических операций в своем приложении. Конечно, есть приложения для z.' * A * z возвращающего скаляр, где z - это комплексный вектор, а A некоторая матрица, даже если это не имеет ничего общего с внутренним произведением и / или двойным векторов.

@jutho не могли бы вы дать пример использования для z.' * A * z ?

Если z1 и z2 являются представлением двух комплексных векторов Z1 и Z2 (например, касательных векторов в голоморфной части касательного пространства кэлерова многообразия ) и a - это матричное представление тензора A с двумя ковариантными индексами (например, комплексная (2,0) -форма кэлерова многообразия), то A(Z1,Z2) = z.' * a * z .

Обратите внимание, что я подчеркиваю здесь, что объекты julia z1 , z2 и a только формируют _представление _ определенных математических объектов (относительно выбранного базиса / координации) и, следовательно, операции со структурами данных не могут быть однозначно связаны с математическими операциями без знания того, что представляют собой эти структуры данных.

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

На данный момент я полностью поддерживаю предложение @StefanKarpinski , которое также в основном повторяется @andyferris выше. В частности

  1. Индексирование стиля APL
  2. v 'дает какой-то тип Covector или Transpose

Все остальное - мелочь. Было бы неплохо добавить функции row(M,i) и col(M,i) . В противном случае, чтобы извлечь строку как ковектор, я думаю, вам понадобится M[i,:].' ? IIUC, M[i,:]' будет делать conj не нужны в этом случае?

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

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

Я думаю, вы хотите взять буквальные значения матрицы. Используя обозначения Дирака, разверните (любую) матрицу M = sum_i | i>например, [0,0,1, ..., 0], мы хотим извлечьU мы получаем row(U,i)' = col(U’,i) что имеет смысл для извлечения левого и правого собственных векторов из собственного разложения.

Энди

15 марта 2015 года в 21:36 Джефф Безансон [email protected] написал:

На данный момент я полностью поддерживаю предложение @StefanKarpinski https://github.com/StefanKarpinski , также в основном поддержанное @andyferris https://github.com/andyferris выше. В частности

Индексирование стиля APL
v 'дает какой-то тип Covector или Transpose
Все остальное - мелочь. Было бы неплохо добавить функции row (M, i) и col (M, i). В противном случае, чтобы извлечь строку как ковектор, я думаю, вам понадобится M [i ,:]. ' ? IIUC, M [i ,:] 'будет делать нежелательное соединение в этом случае?

-
Ответьте на это письмо напрямую или просмотрите его на GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -81228816.

Есть ли добровольцы, которые начнут работать над этим? например @mbauman , @jakebolewski, которых я удивлен, что они еще не в этой теме :)

Поиск всего, что нужно изменить, может быть утомительным, но основное изменение поведения индексации не должно быть слишком плохим. Вероятно, @jiahao и @andreasnoack могут больше рассказать о том, как следует включать Covectors, например, каким должен быть их супертип, если вообще что-нибудь.

Нам нужно 9, не более 8 комментариев, прежде чем мы сможем продолжить.

Я могу помочь с этим.

мы довольно близки

В качестве соответствующего комментария, если будет тип оболочки Transpose и CTranspose , если они также будут простым типом оболочки Conjugate , так что conj(A) будет тоже ленивый. Для умножения матриц с помощью BLAS это не очень полезно, поскольку для этого нет специальной поддержки (а C в BLAS означает эрмитово сопряжение), но если когда-либо будет полная реализация Julia BLAS, было бы здорово также поддержать conj(A)*B без явного спряжения.

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

Возможно, @andreasnoack и @simonbyrne скажут нам, нужно ли пересмотреть # 6837.

Я согласен с @simonbyrne, что у нас не должно быть Transpose{Array} <: AbstractArray .

Другие общие мысли:

  • Я понял, что расчет внешнего продукта в настоящее время содержит исключение из правила «не добавлять автоматически конечные одноэлементные измерения». Если u имеет размер (n,) , u * u' - это вычисление с участием (n,) x (1, n) . Этот продукт не может быть вычислен с использованием обычных правил умножения матриц _, если_ мы автоматически не изменим форму первого аргумента на (n, 1) .
  • Правило «автоматически добавлять конечные одноэлементные измерения» семантики индексирования MATLAB принципиально несовместимо с правилом «транспонирование отменяет все измерения». Согласно предыдущему правилу, массивы формы (n,) семантически идентичны преобразованным массивам формы (n,1) и формы (n,1,1) и т. Д. Но обратите внимание, что если транспонирование меняет все измерения на противоположное, то результирующие массивы имеют форму (n,) , (1, n) и (1,1,n) , которые _не могут_ быть эквивалентными, если вам разрешено добавлять только конечные одиночные символы. Если довести это до логического предела, транспонированный массив может иметь произвольное количество ведущих синглтонов и, следовательно, иметь неоднозначную форму, которая логически несовместима.

Я также провел обзор литературы и обнаружил интересную историю APL. То, что мы назвали правилом индексации APL, не было в книге Айверсона 1962 года, но действительно существовало в APL \ 360 (1968 год; первая реализация APL). Однако APL \ 360 объединил скаляры и 1-векторы, противоречие, которое не признавалось в литературе до (Haegi, 1976). Обсуждение формальной семантики многомерных массивов впервые появилось в докторской диссертации Брауна (1972; он позже разработал APL2) и стимулировало направление работ по формализации их семантики.

Отличный обзор разработок, ведущих к APL2:

  • Карл Фриц Рюер. «Обзор расширений APL». В Трудах Международной конференции по APL, APL '82, страницы 277–314, Нью-Йорк, Нью-Йорк, США, 1982. ACM.

В литературе обращают внимание на правила индексации:

  • Т. Подробнее. «Аксиомы и теоремы теории массивов». IBM Journal of Research and Development, 17 (март): 135–175, 1973.

    • Гигантский фолиант, формализующий семантику многомерных массивов с использованием аксиоматической теории множеств Куайна, показывающий, как скаляры могут быть объединены с массивами ранга 0 с понятием самодостаточных множеств для построения того, что в литературе APL называется «плавающими массивами». ([1] == 1, в отличие от «заземленных массивов», где [1]! = 1. Более поздняя работа Шейлы М. Синглтон в ее магистерской диссертации 1980 года показала, что теория массивов Мора также может быть адаптирована для описания заземленных массивов.)

    • Мор также упоминает внутренний продукт, возвращающий скаляр, как важное правило для управления семантикой массива.

    • Мор также ссылается на сложность обработки "вверх / вниз" для общих многомерных массивов:

      "Пусть V - n-мерное векторное пространство над полем. Не обращая внимания на соображения контравариантности и ковариантности, тензор валентности q на V является полилинейным отображением декартова произведения списка VV ... V длины q в вектор пространство. Если V имеет базу, то тензор валентности q на V может быть представлен _компонентным тензором_, который представляет собой массив на q осях, каждая из которых имеет длину n ".

  • Г. Льюис. «Новая система индексации массивов для APL», Труды седьмой международной конференции по APL - APL '75, 234-239, 1975.

    • Эта статья была первой, в которой индексируемый кортеж в операции индексирования был предложен как объект первого класса в APL, и отмечалось, что согласованное построение результата индексации может быть достигнуто с помощью систематических декартовых произведений по каждому рангу индексного кортежа.

  • Ханс Р. Хэги. «Расширение APL на древовидные структуры данных». ACM SIGAPL APL Quote Quad, 7 (2): 8–18, 1976.

    • Эта статья жаловалась на несоответствие в классическом APL \ 360, который объединяет скаляры и 1-массивы, и защищал, что правило индексации APL требует, чтобы это объединение не выполнялось.

    • Эта статья также содержит конструкцию, очень похожую на Lewis, 1975; работа кажется независимой.

  • JA Gerth и DL Orth. «Индексирование и слияние в APL». В Трудах Международной конференции по APL, APL '88, стр. 156–161, Нью-Йорк, Нью-Йорк, США, 1988. ACM.

    • Отметил, что правило индексации APL может быть оправдано, если рассматривать индексирование как операцию широковещательной передачи, отображающую набор индекса в набор значений. Эта функциональная интерпретация естественным образом предполагает правило сохранения ранга и декартову конструкцию Льюиса и Хэги.

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

image

так как левая часть является скаляром (по определению следа), а правая часть имеет форму (1, n) x (n, n) x (n,) = (1,) . И наоборот, мы можем взять эту идентичность в качестве руководящего принципа для выбора семантики транспонирования векторов. Главное - это цикличность операции трассировки, которая определяет первую идентичность. Величина внутри следа должна быть либо скаляром, либо матрицей (след вектора не определен). Avv' уже однозначно является матрицей: (Av)v' и A(vv') дают тот же результат. Но также, исходя из второй величины, v'Av должна быть матрицей _или_ скаляром. (Если скаляр, второе тождество также выполняется.) v'Av может быть 1-вектором только в том случае, если активно правило «можно добавить конечные одиночные измерения», и в этом случае его можно прозрачно преобразовать в матрицу 1x1.

Таким образом, если мы хотим, чтобы след был определен, он обязательно налагает ограничения на допустимые формы внешних продуктов vv' и квадратичных форм v'Av .

@jihao : Я не совсем понимаю, против чего вы

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

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

Правило «можно добавлять конечные одиночные измерения» используется MATLAB и (согласно @alanedelman) было введено для поддержки многомерных массивов. Вычисление индекса-смещения, используемое в массивах MATLAB, определяется параметром sub2ind , который возвращает тот же линейный индекс независимо от того, сколько конечных единиц вы бросаете на него. Кроме того, в документации по индексированию

Количество нижних индексов, указанное для B, не включая конечные индексы, равные 1, не превышает ndims (B).

Более формально я думаю, что это можно сформулировать так:

Для операций, принимающих массивы в качестве входных данных, массивы (n,) -arrays, (n,1) -arrays, (n,1,1...) находятся в одном классе эквивалентности в отношении того, являются ли допустимые аргументы для этих операций. (Если n=1 , то класс эквивалентности также включает скаляры.)

Примеры:

  • A*b и A\b где A - это матрица, а b может быть вектором или матрицей (идентичная семантика для n x 1 матриц),
  • hcat(A, b) где A - это матрица, а b может быть вектором или матрицей

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

Пока Transpose{A<:AbstractArray} не является подтипом AbstractArray я думаю, что не вижу проблемы (но, вероятно, я что-то упускаю из виду, поскольку я не думал об этом так много, как вы, ребята) :
с участием

typealias AbstractVectorTranspose{A<:AbstractVector} Transpose{A}
typealias AbstractMatrixTranspose{A<:AbstractMatrix} Transpose{A}
typealias AbstractTMatrix Union(AbstractMatrix, AbstractMatrixTranspose} 

(и аналогично для CTranspose ) мы можем иметь

AbstractVectorTranspose * AbstractVector = Number
AbstractVector * AbstractVectorTranspose = AbstractMatrix
AbstractVectorTranspose * AbstractTMatrix = AbstractVectorTranspose
AbstractTMatrix * AbstractVector = AbstractVector
AbstractTMatrix * AbstractTMatrix = AbstractTMatrix

Единственный открытый вопрос заключается в том, следует ли поддерживать AbstractVector * AbstractTMatrix , если AbstractTMatrix имеет 1 в качестве первого размера, или достаточно ли AbstractVector * AbstractVectorTranspose .

Кроме того, новая система типов может помочь в более тщательном выражении некоторых из этих typealias и union sa.

Кроме того, если, например, v.'*A вычисляется как (A.'*v).' , тогда появляется необходимость в оболочке Conjugate , если A сам по себе, например, A=B' .

Я согласен с @simonbyrne, что у нас не должно быть Transpose{Array} <: AbstractArray .

Можете ли вы там уточнить? Я думал, что мнение в https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215 заключалось в том, что CoVector не должен быть подтипом AbstractVector, но мне показалось бы немного странным отсутствие Transpose{Matrix} <: AbstractArray .

Что бы это ни стоило, я думаю, что CoVector основном должно вести себя как Vector за исключением того, что Vector преобразуется в Matrix как матрицу столбцов, а CoVector преобразуется в Matrix как матрицу-строку.

Полагаю, это будет означать, что индексирование в ковектор должно работать так же, как в матрице строк?

Это интересная мысль. Становится ли все проще или сложнее, если в типах транспонирования / ковектора отбрасываются только _ ведущие_ одиночные измерения?

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

@mbauman :

Становится ли все проще или сложнее, если в типах транспонирования / ковектора отбрасываются только ведущие одиночные измерения?

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

@tkelman :

Я думал, что в # 4774 (комментарий) было высказано мнение, что CoVector не должен быть подтипом AbstractVector, но мне показалось бы немного странным отсутствие Transpose {Matrix} <: AbstractArray.

Мне стало ясно, что эта проблема полностью связана с отделением семантики массива (cf # 10064) от семантики линейной алгебры и поиском мест, где они смешиваются.

  • Семантика массива определяется такими функциями, как size, length, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • Семантика линейной алгебры определяется такими функциями, как +, -, *, /,, ', trace ...

Если мы определим функции cat как часть основного интерфейса AbstractArray , тогда Transpose{<:AbstractArray} явно не AbstractArray потому что их поведение конкатенации отличается . Если рассматривать только форму и индексацию как часть основного интерфейса, ситуация становится менее ясной.

Если нам требуется конкатенация как часть основного интерфейса AbstractArray , тогда также легче обосновать, почему типы вроде SymTridiagonal не являются AbstractArray s, поскольку операции конкатенации над SymTridiagonal s типа [SymTridiagonal(randn(5), randn(4)) randn(5)] в настоящее время не определены.

@toivoh :

Полагаю, это будет означать, что индексирование в ковектор должно работать так же, как в матрице строк?

Существуют идентификаторы, которые предполагают, что поведение индексации Transpose{Vector} должно быть таким же, как у обычного Vector s. Учтите, что для числовых типов v[1] дает тот же результат, что и v' * e₁ = v ⋅ e₁ а v[1:2] дает тот же результат, что и v' * [e₁ e₂] , где e₁ - канонический базис Vector{Int} [1, 0, 0, ...] и e₂ равен [0, 1, 0, 0, ...] . Если мы потребуем, чтобы эти тождества, относящиеся к индексации, внутренним продуктам и транспонированию, сохранялись, то можно было бы утверждать, что

(v')[1] == (e₁' * v'') == (v' * e₁)' == (v ⋅ e₁)' == conj(v ⋅ e₁)* = conj(v[1])

(где первый шаг - это новая аксиома, а четвертый использует тот факт, что транспонирование скаляра не выполняется), так что индексация в Transpose{Vector} существу игнорирует транспонирование, а индексация в CTranspose{Vector} будет сопрягать проиндексированные элементы.

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

  • Семантика массива определяется такими функциями, как size, length, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • Семантика линейной алгебры определяется такими функциями, как +, -, *, /,, ', trace ...

+1 к этой точке зрения и не иметь Transpose <: AbstractArray . Кроме того, при индексировании ковектора он должен быть с одним индексом, поскольку в противном случае результат covector * vector (сжатие по одному индексу) не может привести к скаляру (объект с нулевыми индексами).

@jihao : Я не уверен, что понимаю, зачем нам это нужно или нужно

(v')[1] == (e₁' * v'')

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

И +1 к тому, чтобы рассматривать ковекторы как связанные с линейной алгеброй.

Но нет причин, по которым не следует определять конкатенацию с SymTridiagonal , верно?

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

Я думал, что линейная индексация связана с порядком хранения, и в любом случае нет другого разумного порядка для линейной индексации вектора, верно? (И извините за опечатку.)

В памяти нет единственного разумного порядка обхода. Даже для массивов Fortran вы можете выбрать для хранения элементов в основном порядке столбцов, основных строках или даже обратном порядке основных столбцов (что и делал оригинальный компилятор IBM Fortran I). Кроме того, существуют другие структуры данных (см. №10064), такие как попытки, которые можно использовать как массивы и иметь еще больше вариантов для порядка обхода.

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

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

Индексирование объектов Transpose может быть запрещено. Я не говорю, что они должны быть индексируемыми, но если это так, они не обязательно должны иметь такое же поведение индексации. Чтобы быть консервативными, мы можем пока оставить индексирование неопределенным и посмотреть, появятся ли какие-либо варианты использования.

Многие чистые реализации функций линейной алгебры Julia захотят проиндексировать их в Transpose, не так ли? Написание чистого умножения матриц Джулиа (для числовых типов, отличных от BLAS) становится простым, если вам не нужно различать любой возможный случай (нормальный, транспонированный, ctranspose, конъюнктурный?) Для двух задействованных матриц, а просто рассматривать их как обычные матрицы. Можно попробовать методы, не обращающие внимания на кэш, чтобы получить образец доступа к локальной памяти.

Хорошо, да.

@ Юто : Я согласен. И чтобы там поместиться, ковекторы должны индексироваться как матрицы строк, верно?

@toivoh , если вы имеете в виду, что перед ними должен стоять дополнительный индекс 1, я не согласен и не понимаю, как это подразумевается в моем заявлении. Я говорил только о матричных матричных продуктах. Матрица * вектор или ковектор * матрица - это разные методы, требующие разных определений функций, не только потому, что они имеют другой шаблон доступа к памяти, но также потому, что они имеют другой тип возвращаемого значения (Matrix_vector = vector или covector_matrix = covector), так что это очень практичная причина в Юлии не смешивать эти вещи.

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

@Jutho : Хорошо, я согласен, что это, вероятно, не имеет значения, поскольку тип возвращаемого значения в любом случае должен быть другим.

Вот попытка описать то, что мы пытаемся сделать, и дать некоторые аксиомы:

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

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

Вот мой взгляд на скаляры и векторы (как видно с точки зрения чистой матрицы): скаляр - это матрица, которая по своему типу ограничена размером 1 x 1. Вектор - это матрица, которая по своему типу ограничена быть nx 1. (ниже я буду утверждать, что ковектор - это матрица, которая по своему типу ограничена равной 1 x n.) С этой точки зрения мы можем дать две аксиомы (не очень формально описанные ниже)

  • Расширение: рассмотрим функцию от матриц к матрицам. Если он всегда будет создавать выходную матрицу размером 1 в данном измерении, учитывая входные данные определенных типов, этот факт будет закодирован в типе выходных данных (что делает его скаляром / вектором / ковектором).
  • Уточнение: если функция, которая будет принимать аргумент матрицы в настройке чистой матрицы, требует, чтобы вход имел размер один в одном или нескольких измерениях, она может отказаться принимать входные данные, если этот факт не закодирован в типе.

Если мы согласны с вышеизложенным, транспонирование вектора должно быть типом ковектора, описанного выше: Транспонирование матрицы тревоги 1 дает матрицу 1 xn. Если мы закодируем тот факт, что размер по первым измерениям результата всегда равен единице, мы получим ковектор, как описано выше.

Если вы начнете с точки зрения матричной алгебры, то то, что вы говорите, будет правильным. Это модель, которую MATlab, вероятно, реализует довольно идеально. Все представляет собой матрицу. Это закрытая система, все операции с матрицами производят новые матрицы.

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

Я думаю, что операции линейной алгебры в Julia довольно прочно укоренились в традиции Matlab, с некоторыми заметными исключениями, такими как наличие скаляров и векторов, а не попытки переубедить пользователя. Все, что уходит слишком далеко от этого, скорее всего, приведет к очень большим сбоям.

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

Я согласен с @Jutho здесь; весь смысл этой проблемы состоит в том, чтобы отойти от семантики MATLAB «все является матрицей». Правило MATLAB «может добавлять конечные одиночные измерения» необходимо для того, чтобы вселенная была закрыта при операциях линейной алгебры, но это правило определяет классы эквивалентности, которые содержат элементы типа T и другие элементы типа Array{T,N} для все N , и это основная причина, по которой система типов MATLAB неразрешима. (См. Теорему 1 Джойши и Банерджи, 2006 г. - хотя результат сформулирован в терминах форм, на самом деле проблема заключается в том, как изменение ранга массива может изменить семантику программы.)

Но я все еще думаю, что у нас был довольно хороший консенсус относительно того, что умножение скаляров, векторов, ковекторов и матриц должно быть ассоциативным (за исключением таких вещей, как (v'*v)*v где два нескаляра умножаются для получения скаляра) и например, v'*M*v должен создать скаляр, M*v вектор и v'*M ковектор. Я не уверен, насколько еще можно отклониться от семантики «все есть матрица», при этом соблюдая эти условия.
Чего еще мы пытаемся избежать и какие свойства мы хотели бы получить вместо этого?

Насколько было бы хуже, если бы мы только в некоторых случаях объединяли T и Array{T,0} ? (Например, индексирование: M[:,2] дает Array{T,1} , но M[2,2] не дает Array{T,0} )

Мы все еще можем поддерживать допустимую семантику с помощью Transpose{Vector} .

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

Проблема с семантикой типа Matlab заключается в том, что M*v' и v*M иногда работают, хотя и не должны. Если M равно m x 1 тогда M*v' является допустимым и возвращает внешнее количество, подобное продукту (поскольку v' - это 1 x n ). Точно так же, если M равно 1 x m и у нас есть правило «можно добавлять конечные одиночные символы», тогда v*M можно оценить как произведение n x 1 и 1 x m Матрицы

Вопрос об объединении T и Array{T,0} также поднимался в литературе по APL - в APL многомерные массивы рекурсивно вложены, что поднимает вопрос о том, являются ли Array{T,0} и T различимы. В противном случае это «заземленные массивы» (рекурсивные до T ), в противном случае они являются «плавающими массивами» (рекурсивные только до Array{T,0} ). Я считаю, что More, 1973 фактически доказал, что любой выбор аксиоматически непротиворечив. Я не уверен, решила ли APL когда-либо вопрос о том, что использовать, прежде чем большинство практикующих уйдут на пенсию или перейдут к чему-то другому.

@jiahao : Я не понимал, насколько фундаментальным было ваше наблюдение, что

v[i] = e_i' * v

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

M[i,j] = e_i' * M * e_j

что указывает на то, что внутренний продукт с базисным вектором справа соответствует индексации по второму измерению. Таким образом, я бы утверждал, что запись i th ковектора v' должна быть проиндексирована как

v' * e_i = v'[1, i]

где, конечно, мы хотели бы написать что-то еще, кроме 1 в качестве первого индекса, но что?
В любом случае, поскольку мы разрешаем

e_i' * v = v[i] = v[i, 1]

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

v' * e_i - это скаляр, поэтому e_1' * (v' * e_i) - это ковектор, а не скаляр. Таким образом, размеры не совпадают, чтобы думать о v'[1, i] = e_1' * v' * e_i

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

Да, матричное индексирование - это следующий логический шаг, а e_i' * M * e_j на самом деле является одним из выражений, в которых становится полезной аксиома ассоциативности, поскольку

(e_i' * M) * e_j = m_i' * e_j

e_i' * (M * e_j) = e_i' * m_j

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

Я считаю, что для последовательного решения этой проблемы может потребоваться запретить такие выражения, как v[i, 1] , поскольку правило, разрешающее такое поведение индексации
a) должны вызывать ложные случаи для A*v' и v*A (первое работает, а второе - нет, потому что мы применяем конечное одноэлементное правило непоследовательно), и
б) если мы рассмотрим равенство v[i] = v[i, 1, 1, 1] то соответствующее правило индексации ковекторов будет выглядеть как (v')[1, 1, 1, i] и нужно иметь соответствующее правило «разрешить произвольное количество ведущих синглетонов» для ковекторов для согласованности. Меня очень беспокоит отсутствие однозначно определенного первого измерения.

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

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

Я не полностью усвоил все это, но мой главный вопрос прост: равно ли size(covector) (n,) или (1,n) ?

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

Хотя из практических соображений я предполагаю, что он будет определен (например, для чисел и т. Д.), И мой голос идет на (n,) . С точки зрения хранилища / контейнера, я бы сказал, что нет различия между векторами и ковекторами. Таким образом, также нет необходимости использовать ковекторы в качестве контейнеров, и поэтому они не принадлежат иерархии AbstractArray . Это просто тип-оболочка, чтобы выразить, что они ведут себя иначе, чем векторы, в отношении операций линейной алгебры.

«Пока алгебра и геометрия были разделены, их прогресс был медленным, а их использование ограниченным; но когда эти две науки были объединены, они предоставили друг другу общие силы и вместе пошли к совершенству». - Жозеф Луи Лагранж

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

http://ocw.mit.edu/resources/res-8-001-applied-geometric-algebra-spring-2009/lecture-notes-contents/

Собственно, если подумать, справедливее будет сказать, что

e_i' * x = x[i, :] # x is a vector or matrix
x * e_j  = x[:, j] # x is a covector or matrix

Тогда для индексации матрицы мы имели бы

e_i' * M * e_j = e_i' * (M * e_j) = e_i' * M[:, j] = M[:, j][i, :] = M[i, j]
e_i' * M * e_j = (e_i' * M) * e_j = M[i, :] * e_j  = M[i, :][:, j] = M[i, j]

В настоящее время это не совсем верно в Julia, например, v[i, :] настоящее время создает массив 1x1, а не скаляр. (Но, может быть, и не должно)
Ассоциативность умножения матриц в e_i' * M * e_j соответствует коммутативности разрезания по разным размерностям M[:, j][i, :] = M[i, :][:, j] , что кажется желательной функцией.
Судя по приведенной выше строке, мы должны иметь

v'[:,i] = conj(v[i])

@Jutho : Я думаю, что эта парадигма «индексирования как повторного нарезания» действительно распространяется на массивы / тензоры более высоких измерений: вы применяете одно нарезание для каждого измерения для индексации в любом порядке. Это соответствует серии сокращений с тензорами первого порядка, соответствующими e_i и т. Д., Также в любом порядке (если вы отслеживаете, какие измерения совпадают).

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

Да, я очень хорошо знаком с тензорными сокращениями и т. Д., И действительно получение матричного элемента / тензорного элемента действительно соответствует принятию ожидаемого значения в стандартной вычислительной базе, то есть сокращению с некоторыми базисными векторами (если у вас есть ортогональный базис по крайней мере ), но нет однозначной ассоциации относительно того, соответствует ли индекс сокращению с e_i или с e_i' . Это соответствует принятию решения о том, находится ли соответствующий индекс в ковариантной или контравариантной позиции, и однозначного решения не существует. У всех возможных комбинаций верхних и нижних индексов есть свои полезные приложения. Но заключение контракта с e_i и e_i' - это даже неправильный способ постановки этого вопроса с математической точки зрения, потому что, как я сказал выше, на самом деле нет такого математического отображения, как транспонирование вектора. Транспонирование - это операция, определенная для линейных карт (матриц), и даже там она соответствует транспонированию матрицы только в том случае, если вы также записываете двойные векторы как векторы-столбцы. Транспонирование вектора - это просто удобный трюк, который был введен в матричную алгебру (где действительно векторы - это матрицы n x 1 ), и он работает только потому, что вы находитесь в евклидовом пространстве, где есть каноническое отображение из (сопряженного ) векторное пространство в двойственное пространство.

В частности, если вам нужны свойства, которые вы описываете выше, вы должны иметь, что M[i,:] возвращает другой объект (либо матрицу 1xn или ковектор), затем M[:,i] (который должен быть матрицей или вектором nx1 ). Тот факт, что это явно не обобщается на более высокие измерения, является одним из основных вопросов для обсуждения этого вопроса, и кажется, что большинство людей поддерживают индексирование APL, при котором измерения, индексированные с помощью числа, отбрасываются (то есть оба M[:,i] и M[i,:] создают массив ранга 1, следовательно, вектор). Индексирование - это свойство массивов, и именно это смешение поведения индексации с операциями линейной алгебры, в первую очередь, приводит к путанице / несогласованности. Он может быть последовательным, пока вы остаетесь в замкнутой экосистеме объектов ранга N=2 , т.е. все является матрицей, а также векторами и числами, и вы никогда не рассматриваете массивы более высокой размерности.

Я только что понял, что M[i,:] должен создать ковектор по моим рассуждениям выше, как вы говорите. Таким образом, кажется, что наличие ковекторов на каком-то уровне принципиально несовместимо с индексированием APL. Если мы отдаем предпочтение индексации APL (а мне это нравится), возникает вопрос, где провести грань между этим миром и миром, в котором живут ковекторы. (Я надеялся, что можно будет примирить их, может быть, остальные из вас уже поняли, что нам придется отказаться от этого?)

Этот конфликт, возможно, не так уж и удивителен, если задуматься:

  • При индексировании APL в результате сохраняются только значимые индексируемые измерения; остальные выдавлены.
  • Если бы мы сделали это с помощью v' то у нас было бы v' = conj(v) . Ковектор вместо этого можно рассматривать как отслеживающий недостающее первое измерение.

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

Идея, кажется, не в том, чтобы сделать их <: AbstractArray . Имеет ли смысл сделать их подтипами нового типа LinearOperator ? (Я знаю, что это уже обсуждалось ранее, но не совсем помню выводы.)

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

@toivoh Это важное соображение, поэтому я предложил новые функции, такие как row(M,i) и col(M,i) для извлечения вектора или ковектора i th из матрицы. Эти функции предназначены только для двумерных массивов M и для людей, интересующихся матричной алгеброй. Поначалу это может показаться немного менее очевидным, чем индексация в стиле MATLAB, но в целом концептуальное разделение идеи вектора и его двойного / транспонированного / ковекторного помогает прояснить ситуацию. В случае квантовой механики целая область перескочила к обозначениям Дирака на скобках просто потому, что это различие между вектором и ковектором очень важно для комплексных векторов, и обозначения Дирака делают это очевидным. Я надеюсь, что Джулия тоже сделает это, а также станет хорошим инструментом для массивов хранения более высоких измерений и линейной алгебры более высоких измерений! (Потому что мы жадные, правда?)

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

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

(1) Массивы - это, по сути, контейнеры хранения, которые будут использовать все пользователи Julia, и нам нужна наилучшая семантика для этого. Правила в стиле APL кажутся мне в любом случае очень хорошим решением. С другой стороны, минимально-двумерные массивы в стиле MATLAB с допущениями с замыкающими одномерными индексами просто кажутся неестественными, сбивающими с толку, и, как сказал Юто, они могут даже привести к небрежному программированию, когда вы не отслеживаете должным образом размер вашего массива. Я бы зашел так далеко, чтобы сказать, что для вектора v код v[i,:] должен выдавать ошибку, поскольку v является одномерным. Поэлементные операции, такие как + и .* , полезны для любого контейнерного класса, а не только в матричной или полилинейной алгебре. Единственная уступка, которую люди делают в отношении контейнеров для хранения вещей ниже, - это дополнительные точки на .* и т. Д.

(2) Большинство, но не все, пользователи Julia будут использовать матричную алгебру, поэтому мы добавляем некоторый синтаксический сахар для некоторых векторных и матричных операций, в основном с помощью символа * (но у нас может быть матричное деление и т. Д.). В этой системе мы берем одномерные и двумерные массивы - векторы-столбцы и матрицы соответственно. Для полнофункциональной матричной системы нам также потребуются векторы-строки (ковекторы) и операция транспонирования ' . Вектор-строка - это во всех возможных смыслах одномерный массив, и я утверждаю, что он должен быть проиндексирован как таковой (и, конечно, не как covec[1,i] !!). С правилами APL мы вынуждены сделать некоторые различие между векторами и ковекторами, и система типов идеальна для этого (помните, нам повезло, что мы уже могли повторно использовать матрицу и вектор в качестве типов одномерных и двумерных массивов, а не типа оболочки ... в принципе, мы могли бы их тоже завернуть, но не вижу в этом смысла). С помощью системы типов компилятор может определить, что CoVector * Vector - скаляр, а Vector * CoVector - матрица, и так далее. Как сказал Тойво, _с точки зрения MATLAB CoVector точно отслеживает «отсутствующее» первое измерение. _ Обычным пользователям не нужно создавать эти объекты; они просто вводят векторы и матрицы и используют операции * и ' . Неравнодушные люди заметят и оценят различие. _Biggest_ изменение для пользователей заключается в необходимости изменить M[i,:] на использование новой функции row(M,i) или преобразовать его в класс оболочки с чем-то вроде Transpose(M[i,:]) или M[i,:]' - это можно рассматривать как преимущество, потому что, как и в нотации Дирака, вы никогда не забываете, какие объекты являются векторами, а какие - ковекторами, а присвоение объектам с помощью утверждений типа вызовет ошибки там, где это необходимо. Я думаю, что стоит обсудить эту нотацию. В будущем мы можем даже расширить систему оболочек, чтобы обеспечить эффективную отложенную оценку. Насколько я понимаю, это беспроигрышный вариант для всех.

(3) Некоторые из нас интересуются полилинейной алгеброй, и им пришлось изучить разницу между двойственным пространством и транспонированием и т. Д. Юто коснулся этого. Массивы Джулии уже являются отличными устройствами хранения для многомерных тензоров, и дополнительные пакеты (которые могут или не могут найти путь в Base) будут использоваться такими людьми, как мы. Нам не нужны и не нужны такие операции, как ' или * определенные для массивов размерности больше двух. Я не вижу варианта использования ' , ориентированного на хранение, который нельзя было бы сделать более аккуратно, явно переупорядочив индексы. Сама идея конечных одномерных индексов только делает концепцию менее привлекательной ... поэтому, пожалуйста, оставьте ' для одномерных и двумерных массивов - как MATLAB :) (видите, у меня есть приятные вещи сказать про MATLAB ...)

Для полнофункциональной матричной системы нам также нужны векторы-строки (ковекторы) и операция транспонирования ».

Это заявление действительно затрагивает суть проблемы. Индексирование, транспонирование и * продукты смешиваются. Кроме того, невозможно согласовать полную семантику векторов-строк с семантикой ковекторов без дальнейшего введения такой функции, как row(A, i) чтобы вернуть i th строку из A как ковектор, а не одномерный массив. В настоящее время A[1, :] хочет означать и «взять первую строку A», и «взять первый срез по второму измерению A», но эти понятия принципиально несовместимы в предложении ковекторов.

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

Я считаю, что вы слишком бойки с этим заявлением. Предыдущее обсуждение совершенно ясно установило, что если вам нужна полная семантика линейной алгебры (с правильными произведениями и транспонами), то вектор-строка не может иметь того же типа, что и одномерный массив. Во-первых, индексирование в ковектор должно возвращать комплексные сопряженные значения, (v')[1] = conj(v[1]) , для согласованности с внутренним продуктом dot . Во-вторых, ковекторы не являются векторами, это линейные функционалы, ожидающие создания внутреннего продукта с вектором. В-третьих, векторы и ковекторы имеют разное поведение конкатенации массивов в hcat и vcat . По всем этим причинам вектор-строка не может быть «во всех возможных смыслах одномерным массивом».

Я был бы за то, чтобы избавиться от замыкающих синглтонов: я не думаю, что когда-либо видел код Julia, который использовал бы это. Первоначально я собирался сказать, что он нам понадобится для 0-мерных массивов, но я вижу, что X[] отлично работает.

Я думаю, что индексирование APL вместе с функцией row(M,i) для векторов строк имеет наибольший смысл и кажется хорошим компромиссом. Я не уверен в векторной индексации строк, но мне _не_ нравится (v')[1,i] .

Другое важное решение, которого мы еще даже не коснулись, - это типы. Мой единственный вывод из того, что я попробовал это раньше, заключается в том, что очень трудно сделать так, чтобы v' было AbstractVector , поскольку это делает отправку беспорядочной.

Возможны два варианта:

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

    • Transpose <: AbstractMatrix

    • CoVector <: Any

    • своего рода транспонирование для Factorization объектов.

  2. Мы используем один и тот же тип для всех транспозиций, но X' _not_ не является AbstractMatrix

    • Transpose <: Any

Для сопряжения мы можем либо

а. определить ConjugateTranspose (вместе с ConjugateCoVector если мы воспользуемся вариантом 1 выше)

б. используйте тип оболочки Conjugate и вложите его соответствующим образом: нам потребуется некоторое соглашение относительно того, используем ли мы Transpose{Conjugate{T}} или Conjugate{Transpose{T}} .

Мне нравится, когда Transpose{Matrix} не является подтипом AbstractMatrix . Я думаю, что ближайший аналог, который у нас есть в base, - это особый тип матрицы, такой как Symmetric , который ведет себя алгебраически как матрица, но не в своей семантике индексации. (# 987 установил, что без множественной наследственности или свойств Holy иерархия типов должна уважать семантику контейнера над алгебраической семантикой.)

Проблема составления типов, которые по сути являются «семантическими тегами», также обнаружилась в # 8240. Я думаю, что Transpose{Conjugate{T}} было бы предпочтительнее, так как конъюгация - это понятие, которое проникает в базовое поле элементов.

Вот примеры, которые иногда следуют тому же правилу конечного одиночного элемента, что и MATLAB, а в других случаях - нет:

  • Конечные одиночные символы разрешены в операциях индексирования. (Как MATLAB)
julia> (1:5)[5,1,1,1,1,1,1]
5
  • Завершающие срезы не допускаются в операциях индексирования. (В отличие от MATLAB, который позволяет им.)
julia> (1:5)[5,:]
ERROR: BoundsError()
 in getindex at abstractarray.jl:451
  • Для массивов rank> = 3 неявные замыкающие одиночные пары добавляются к операции индексированного присваивания, когда индексов меньше, чем ранг массива, а последний индекс является скаляром (например, MATLAB):
julia> A=zeros(2,2,2); A[1,2]=5; A #Same as A[1,2,1]=5
2x2x2 Array{Float64,3}:
[:, :, 1] =
 0.0  5.0
 0.0  0.0

[:, :, 2] =
 0.0  0.0
 0.0  0.0
  • Для массивов rank> = 3 неявные завершающие _slices_ добавляются к операции индексированного присваивания, когда имеется меньше индексов, чем ранг массива, а последний индекс _не скалярный_ (например, MATLAB):
julia> A[:,:]=3; A
2x2x2 Array{Float64,3}:
[:, :, 1] =
 3.0  3.0
 3.0  3.0

[:, :, 2] =
 3.0  3.0
 3.0  3.0
  • Для массивов rank> = 3 неявные завершающие синглтоны добавляются к операции индексации, когда индексов меньше, чем ранг массива, а последний индекс является скаляром (например, MATLAB):
julia> A=reshape(1:8,2,2,2); A[:,1]
2-element Array{Int64,1}:
 1
 2

julia> A[:,1,1]
2-element Array{Int64,1}:
 1
 2
  • Для массивов с рангом r> = 3 операция индексирования, когда имеется k <r индексов, чем ранг массива, а последний индекс является срезом, неявно линеаризует оставшийся массив rk с рангом. (как MATLAB):
julia> A=reshape(1:8,2,2,2); A[1,:]
1x4 Array{Int64,2}:
 1  3  5  7

julia> A=reshape(1:8,2,2,2); A[1,:,:]
1x2x2 Array{Int64,3}:
[:, :, 1] =
 1  3

[:, :, 2] =
 5  7
  • Завершающие синглтоны не удаляются в операциях присваивания. (В отличие от MATLAB)
julia> A=zeros(1); A[1] = randn(1,1)
ERROR: `convert` has no method matching convert(::Type{Float64}, ::Array{Float64,2})

You might have used a 2d row vector where a 1d column vector was required.
Note the difference between 1d column vector [1,2,3] and 2d row vector [1 2 3].
You can convert to a column vector with the vec() function.
 in setindex! at array.jl:307

julia> A=zeros(1,1); A[1,1] = randn(1)
ERROR: `convert` has no method matching convert(::Type{Float64}, ::Array{Float64,1})
 in setindex! at array.jl:308
  • Джулия не добавляет автоматически завершающее одноэлементное измерение, если это допускает допустимую операцию. (В отличие от Matlab, который делает)
julia> 1/[1.0,] #In MATLAB, interpreted as the inverse of a 1x1 matrix
ERROR: `/` has no method matching /(::Int64, ::Array{Float64,1})
  • Внешние продукты работают и являются исключением из предыдущего правила; их семантика неявно использует конечный синглтон в первом аргументе. (Как Matlab)
julia> [1:5]*[1:5]' # Shapes are (5,) and (1,5) - promoting to (5,1) x (1,5) works
5x5 Array{Int64,2}:
 1   2   3   4   5
 2   4   6   8  10
 3   6   9  12  15
 4   8  12  16  20
 5  10  15  20  25

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

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

Чтобы реализовать это, нам понадобится своего рода треугольная диспетчеризация, иначе я не уверен, как бы вы выразили такие вещи, как «матрица с записями Complex64 или Complex128 , ее транспонирование , или его сопряженное транспонирование ". Особенно если использовать варианты 2 + b выше.

@ esd100 , что из этого неоднозначно или опасно? Все они либо удобны - когда вам воображают «виртуальные синглтоны» и вы их захотели, либо неудобны - когда они вам нужны, а они нет. Ни один из них не имеет двух правдоподобных значений с различным поведением.

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

Я считаю, что вы слишком бойки с этим заявлением.

Правда! Извини @jiahao , я виноват в смене часовых поясов. Слово, которое я хотел написать, это «тензор», и я строго имел в виду поведение индексации [] . Вы правы, что конкатенация - это важное соображение, и я думаю, что естественный способ сделать это (построить матрицу) достаточно очевиден (если в данном случае рассматривать его как размер 1 xn). Ясно, что ковектор - это не «во всех смыслах» 1D Julia Array ... в этом весь смысл ...

Примеры цзяхао также меня беспокоят. Я был бы за удаление любого возможного конечного синглтон-поведения. Линеаризация более поздних измерений может быть полезна, но также может способствовать некоторой лени, когда вы забываете, сколько измерений имеет ваш массив ... (я думаю, это то, что подразумевается под "неоднозначным" и "опасным" ... Я действительно хочу Джулия, чтобы выдать ошибку, когда я обрабатываю 16-мерный массив как 15-мерный, например).

какие из них неоднозначны или опасны?

Четвертый мне кажется неоднозначным и опасным.

julia> A=zeros(2,2,2); A[:,:]=3; A
2x2x2 Array{Float64,3}:
[:, :, 1] =
 3.0  3.0
 3.0  3.0

[:, :, 2] =
 3.0  3.0
 3.0  3.0

Очевидно (для меня) это должна быть синтаксическая ошибка. А - это ранг-3, а третье измерение вообще не упоминалось. Скорее всего, третье измерение было отключено из-за ошибки, и теперь в код внесена трудная для поиска ошибка. Буду признателен, если у него появится ошибка (как в IDL, так и в fortran).

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

или частный случай 1D линейной индексации (так как это очень удобно)

Как легко продаются наши принципы! :)

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

Да, верно. Но, по крайней мере, одномерное индексирование визуально отличается. В то время как индексирование массива 6D с помощью 5D намного меньше. В любом случае, я предпочел бы иметь более строгие правила индексации и отказаться от линейной индексации 1D, чем идти в другом направлении. Тем более, что можно легко получить измененную ссылку на массив, разделяющий память.

Можем ли мы использовать скобки {} (или другие) для линейной индексации и оставить [] для многомерной индексации? Просто идея ...

23 марта 2015 года в 14:36 ​​Боб Портманн на [email protected] написал:

Да, верно. Но, по крайней мере, одномерное индексирование визуально отличается. В то время как индексирование массива 6D с помощью 5D намного меньше. В любом случае, я предпочел бы иметь более строгие правила индексации и отказаться от линейной индексации 1D, чем идти в другом направлении. Тем более, что можно легко получить измененную ссылку на массив, разделяющий память.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -84805310.

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

Хотя этот вопрос, кажется, вызывает много споров, большая часть фактической работы по улучшению индексации проводилась в довольно большом количестве запросов на вытягивание в течение цикла 0,4. Например, теперь, когда у нас есть CartesianIndex и друзья, я не понимаю, зачем нужно разделять синтаксис для линейной и декартовой индексации - действительно, теперь вы можете комбинировать их (# 10524). Я бы тоже хотел избавиться от линейной индексации, но иногда она более производительна (вероятно, во многом из-за # 9080; enumerate и zip страдают той же проблемой). Вероятно, нам следует реализовать fastindex как оболочку вокруг eachindex , как в # 10507.

Если вас интересуют правила индексации, вместо того, чтобы ломать голову над этим вопросом, давайте сосредоточимся на самом интересном. На данный момент это, несомненно, # 10525. В частности, https://github.com/JuliaLang/julia/pull/10525#issuecomment -84597488 требует какого-то разрешения.

@timholy Я действительно не следил за всеми разработками в области быстрой декартовой индексации, и, только что взглянув на base / multidimensional.jl, я вижу, что происходит много метапрограммирования. Есть ли шанс, что у вас будет время написать (или выступить с докладом на JuliaCon) о том, как все это работает?

В некотором смысле, знать не так много: хотя под капотом происходит изрядное количество, идея состоит в том, чтобы сделать его очень простым в использовании. Так что буквально

k = 0
for I in eachindex(A)
     B[k+=1] = A[I]   # B is being linearly-indexed, A is being cartesian-indexed
end

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

Я планирую записать это когда-нибудь в ближайшие месяцы, но если люди действительно хотят знать подробности, возможно, будет разумным выступление на JuliaCon. @Jutho или @mbauman могли бы выступить так же хорошо, как и я.

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

Вероятно, нам следует реализовать fastindex как оболочку вокруг каждого индекса, как в # 10507

@timholy Есть ли причина, по которой eachindex возвращает UnitRange для быстрых линейных массивов? Он по-прежнему будет стабильным по типу, и если вызывающий когда-либо захочет убедиться, что он получит CartesianIndex, он может вручную создать CartesianRange (мы также могли бы добавить метод для eachindex(size(A)) поскольку CartesianRange не экспортируется ).

Это то, что он делал сначала, но я думаю, что @Jutho изменил это. (Если это не так, то я, вероятно, изменил его, не осознавая этого.) Я предполагаю, что изменение было мотивировано ради согласованности (так что вы можете рассчитывать на получение CartesianIndex ), что имеет определенный смысл . Но, как вы отметили, есть альтернативные средства обеспечения этого.

@ Юто , есть мысли?

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

@timholy , в ответ на ваш предыдущий пост, я не могу присутствовать на JuliaCon, поэтому не стесняйтесь говорить о вещах CartesianIndex (в любом случае я внес лишь несколько последних штрихов). Я с нетерпением жду отчетов и видео конференции.

Причудливая мысль: для массива A любого измерения (включая измерение> 2) можно было бы A' циклически переставлять индексы A . Итак, A'[i, j, k] == A[j, k, i] . Это привело бы к обычному транспонированию матриц для 2-мерных массивов, а также к обычному транспонированию для «векторов» строк и столбцов при построении как в MATLAB (то есть как массивы [n, 1] и [1, n] 2d соответственно). Таким образом, он никогда не отобразит «вектор» 2-го столбца в истинный ковектор или «вектор» 2-й строки в истинный вектор. (Я думаю, что это свойство хорошее, но другие могут не согласиться.) Это дало бы тождества A'' == A' и v'' == v _ для матриц и векторов, сконструированных как объекты массива_. Учитывая вид иерархии типов, как обсуждалось выше (где истинные векторы и ковекторы в достаточной мере отличаются от абстрактных массивов), ' все же можно было бы дать совершенно другой метод для истинных векторов и ковекторов, где он соответствует концепции линейной алгебры. и не обязательно удовлетворять v'' == v (но может, если люди решили, что они хотят этого).

Для ясности: я ни на секунду не думаю, что ' _needs_ должен иметь какой-либо метод для массивов размерности> 2, но я не видел этого предложения выше (если только это не означало "изменение индексов ") и подумал, что я просто упомяну об этом. Наибольший вред, который я вижу (на первый взгляд), носит концептуальный характер: объединение (слегка произвольной) комбинаторной операции с оператором, обычно принимаемым как область линейной алгебры. На это можно ответить, что по крайней мере некоторая степень такого смешения неизбежна, когда мы пытаемся расширить линейные алгебраические концепции до концепций, ориентированных исключительно на данные (о чем свидетельствует вся эта дискуссия). Таким образом, мы можем также использовать удобное сокращение для циклических перестановок многомерных массивов в качестве побочного продукта определения того, что ' должен делать в целом для измерений многомерных массивов, если это сводится к ожидаемому случаю в 2 измерения. Можно даже добавить метод, который принимает целочисленный аргумент, чтобы A'(k)[I] циклически переставлял индексы A[I] k раз, давая A'(ndims(A))[I] == A[I] и A'(-k)[I] переставлял индексы в обратном направлении.

Просто мысль.

Если transpose определено для многомерных массивов, я бы предпочел, чтобы он по-прежнему удовлетворял A''=A , т.е. он был обратным. Это прямо противоречит предыдущему предложению.

Это честно. Как я уже сказал, мое предложение является (потенциально) привлекательным только в том случае, если кто-то может позволить разрыву между линейной алгебраической значимостью транспонирования и любым массивоцентрическим значением, которое оно налагает в случае d> 2. Мое рассуждение заключалось в том, что до тех пор, пока этот раскол уже (как бы) происходит, и если бы в противном случае методы были бы полностью неиспользованными, было бы неплохо иметь сокращение для перестановки индексов - если это не требует никаких специальная обработка, чтобы сделать случай d = 2 работоспособным. Как вы (@Jutho) упомянули, это что-то вроде удобного совпадения, что транспонирование измерений в случае 2d имеет линейное алгебраическое значение, которое оно имеет (и только после идентификации (со) векторов как 2d массивов, если на то пошло), поэтому, возможно, мы не Не нужно быть придирчивым к математическим свойствам transpose (например, требовать A'' == A ) для d> 2. Если кто-то хочет пойти по этому пути, существует множество потенциально полезных методов, которые могут быть назначенным, например: A' циклически переставляет один раз, A'(k) циклически переставляет k раз, A'(I) для I::Array{Int, 1} с длиной <= ndims(A) циклически переставляет индексы, перечисленные в I , и A'(p) для p::Permutation длины <= ndims(A) переставляет индексы в соответствии с p . Но я полагаю, что, может быть, лучше всего сделать пакет и посмотреть, достаточно ли он полезен, чтобы завоевать популярность.

Я поддерживаю два изменения / уточнения, которые были упомянуты, например, Стефаном Карпински 16 октября 2014 г. и simonbyrne 22 марта 2015 г., но с одной оговоркой:

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

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

Согласно этому предложению, при работе с матричным умножением и транспонированием, по существу, не будет различий между вектором и матрицей с одним столбцом, а также не будет никаких значимых различий между скаляром, вектором длины 1 и 1-на -1 матрица. Однако x' будет матрицей 1 на n, а не вектором.

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

Ниже приводится гипотетическая стенограмма сеанса Джулии с предлагаемыми изменениями.

Сначала мы определяем скаляр, два вектора и матрицу.

julia> alpha = 2.0
2.0

julia> x = [1.0; 2.0; 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> y = [4.0; 5.0; 6.0]
3-element Array{Float64,1}:
 4.0
 5.0
 6.0

julia> A = [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0]
3x3 Array{Float64,2}:
 1.0  2.0  3.0
 4.0  5.0  6.0
 7.0  8.0  9.0

Умножение скалярного вектора работает, даже если присутствуют посторонние измерения, и результатом всегда является вектор.

julia> alpha*x
3-element Array{Float64,1}:
 2.0
 4.0
 6.0

julia> alpha*x[:,[1]]
3-element Array{Float64,1}:
 2.0
 4.0
 6.0

Транспонирование - это инволюция.

julia> x'
1x3 Array{Float64,2}:
 1.0  2.0  3.0

julia> x''
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> x==x''
true

julia> x'''
1x3 Array{Float64,2}:
 1.0  2.0  3.0

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

julia> A*x
3-element Array{Float64,1}:
 14.0
 32.0
 50.0

julia> A*x[:,[1]]
3-element Array{Float64,1}:
 14.0
 32.0
 50.0

Матрица из одной строки, умноженная на матрицу, равна матрице из одной строки.

julia> x'*A
1x3 Array{Float64,2}:
 30.0  36.0  42.0

Внутреннее произведение является скаляром и производится по более общим правилам для произведений матрица-вектор и матрица-матрица.

julia> x'*y
32.0

julia> x'*y[:,[1]]
32.0

Во внешнем продукте нет ничего особенного.

julia> x*y'
3x3 Array{Float64,2}:
  4.0   5.0   6.0
  8.0  10.0  12.0
 12.0  15.0  18.0

julia> x[:,[1]]*y'
3x3 Array{Float64,2}:
  4.0   5.0   6.0
  8.0  10.0  12.0
 12.0  15.0  18.0

Умножение вектора на вектор является ошибкой.

julia> x*y
ERROR: `*` has no method matching *(::Array{Float64,1}, ::Array{Float64,1})

Умножение вектора на матрицу является ошибкой.

julia> x*A
ERROR: DimensionMismatch("*")
 in gemm_wrapper! at linalg/matmul.jl:270
 in * at linalg/matmul.jl:74

Умножение матриц ассоциативно.

julia> (x*x')*y
3-element Array{Float64,1}:
 32.0
 64.0
 96.0

julia> x'*y
32.0

julia> x*(x'*y)
3-element Array{Float64,1}:
 32.0
 64.0
 96.0

julia> norm((x*x')*y-x*(x'*y))
0.0

РЕДАКТИРОВАТЬ: удалены два примера, связанных с понижением внутреннего измерения в продукте. Они не имели большого отношения к текущему обсуждению.

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

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

Еретическое предложение: что, если бы мы исключили размерность из параметров типа и выполнили бы все проверки размерности / размера во время выполнения? (Мы все равно делаем изрядное количество этого.) И оставим параметр размерности для типов, которые также кодируют размер в качестве параметра типа. (Утки скрывается на две недели и меняет свою учетную запись github на @SomeoneWhoCertainlyIsntTimHolyUhUhNoWay.)

(Конечно, я бы очень пожаловался на себя из-за одного # 10525.)

FWIW, я бы сказал, что это не такая уж безумная идея: например, так работает Torch, и разработчики Torch выразили недовольство кодировкой размерности Джулии внутри системы типов.

@timholy Вопрос: можем ли мы использовать эту

Если бы кто-то все еще был заинтересован в использовании CoVector / DualVector / TransposeVector они все равно должны были бы обернуть наш новый TimHolyArray{DataType} _and_ нам пришлось бы либо разобраться в транспонировать массив размерностью больше двух (или одного), или же запретить построение TransposeVector(tharray) когда tharray имеет размерность больше двух (или одного) ... Фактически, всевозможные вещи должны будут выдавать ошибки на уровне времени выполнения, которые в настоящее время являются ошибками времени компиляции (например, умножения, которые в настоящее время не определены и, следовательно, запрещены системой типов).

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

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

@timholy : Я действительно уверен, так ли это или нет. Бывают ситуации, когда я считаю очень полезным рассылать по измерению.

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

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

Предложение:

  1. Переименуйте текущее поведение умножения матриц в timesfast а текущее поведение транспонирования в transposefast .
  2. Измените (*) и transpose чтобы обрезать конечные размеры одиночного элемента, как в предыдущем комментарии . Например, u'*v станет скаляром, v'' станет вектором, а (v*v')/(v'*v) будет определено.

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

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

Спасибо нескольким участникам за то, что они помогли мне ускориться в этой очень длинной теме!

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

Этот вопрос был о том, как избежать как можно большего числа особых случаев.

Правила, позволяющие подделывать одноэлементные измерения, нарушаются в тот момент, когда одно из одноэлементных измерений становится тем, которое вам действительно нужно. С правилом «усечь [все] конечные одиночные измерения», то если v - это 1-элементный вектор, тогда v' - скаляр, и поэтому v'' также скаляр. Так что даже в этом предложении свойство v == v'' не всегда сохраняется.

Вы можете попытаться изменить правило, чтобы «обрезать только последнее конечное одноэлементное измерение, если размерность массива 2». Но даже с этим модифицированным правилом внешний продукт v * w' не следует автоматически из определения умножения матриц, а вместо этого должен быть собственным определением Vector * Matrix специальном регистре, и определение должно быть «выдать ошибку, если только формы не (N) x (1, M)».

@jiahao :

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

Что касается вашего первого примера ( v==v'' ), я хотел бы в первую очередь избежать построения 1-элементного вектора v .

Что касается вашего второго примера, да, я думаю, что с v*w' следует обращаться так, как вы описываете. При работе с матричным умножением и транспонированием я хочу, чтобы вектор v и матрица N-by-1 v[:,[1]] обозначали один и тот же математический объект, даже если они имеют разные внутренние представления. Это потребует специального кода для обработки матрицы размером N векторов 1 на M.

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

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

Я не уверен, что понимаю эти аргументы, но одна вещь привлекла мое внимание. Возможно, это принятие желаемого за действительное, но было бы хорошо, если бы компьютер мог думать так же быстро, как человеческий мозг. Я имею в виду, что когда Брайан Саттон говорит о программе «выбор предпочтительного представления», я представляю себе программу, которая может думать, как и мы, когда мы занимаемся математикой. Возможно, это невозможно с сегодняшними технологиями и слишком сильно замедлит работу. Но, как было бы хорошо ...

Я физик и активный пользователь Юлии.

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

Но здесь я хотел бы поднять тесно связанный с этим вопрос.

Текущая реализация Julia:

Пусть V - трехмерный тензор ранга 1 (вектор)
V [1] даст нам скейлер, а не одномерный тензор ранга 1.

Пусть A - тензор 3x4x5 ранга 3
B = A [1,:,:] даст нам тензор ранга 3 размером 1x4x5.

Вышеупомянутые два поведения не совсем согласуются.

Я очень предпочитаю следующую функцию индексации / скольжения:

Пусть A - тензор 3x4x5 ранга 3
B = A [1,:,:] даст нам тензор 4x5 ранга 2.
C = A [1: 1,:,:] даст нам тензор ранга 2 размером 1x4x5.
(в настоящее время два вышеуказанных дают одинаковый результат)

Пусть V - тензор ранга 1
B = V [1] даст нам масштабатор
C = V [1: 1] даст нам одномерный тензор ранга 1.

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

Лучший

Сяо-Ган


От: esd100 [[email protected]]
Отправлено: вторник, 9 июня 2015 г., 21:46
Кому: JuliaLang / julia
Копия: Сяо-Ган Вэнь
Тема: Re: [julia] Серьезное отношение к переносу векторных изображений (# 4774)

Я не уверен, что понимаю эти аргументы, но одна вещь привлекла мое внимание. Возможно, это принятие желаемого за действительное, но было бы хорошо, если бы компьютер мог думать так же быстро, как человеческий мозг. Я имею в виду, что когда Брайан Саттон говорит о программе «выбор предпочтительного представления», я представляю себе программу, которая может думать, как и мы, когда мы занимаемся математикой. Возможно, это невозможно с сегодняшними технологиями и слишком сильно замедлит работу. Но, как было бы хорошо ...

-
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

Я имею в виду: C = A [1: 1,:,:] даст нам тензор ранга 3 размером 1x4x5.

Сяо-Ган


От: Сяо-Ган Вэнь [[email protected]]
Отправлено: понедельник, 22 июня 2015 г., 12:01
Кому: JuliaLang / julia; JuliaLang / юлия
Копия: Сяо-Ган Вэнь
Тема: RE: [julia] Серьезное отношение к переносу векторных изображений (# 4774)

Я физик и активный пользователь Юлии.

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

Но здесь я хотел бы поднять тесно связанный с этим вопрос.

Текущая реализация Julia:

Пусть V - трехмерный тензор ранга 1 (вектор)
V [1] даст нам скейлер, а не одномерный тензор ранга 1.

Пусть A - тензор 3x4x5 ранга 3
B = A [1,:,:] даст нам тензор ранга 3 размером 1x4x5.

Вышеупомянутые два поведения не совсем согласуются.

Я очень предпочитаю следующую функцию индексации / скольжения:

Пусть A - тензор 3x4x5 ранга 3
B = A [1,:,:] даст нам тензор 4x5 ранга 2.
C = A [1: 1,:,:] даст нам тензор ранга 2 размером 1x4x5.
(в настоящее время два вышеуказанных дают одинаковый результат)

Пусть V - тензор ранга 1
B = V [1] даст нам масштабатор
C = V [1: 1] даст нам одномерный тензор ранга 1.

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

Лучший

Сяо-Ган


От: esd100 [[email protected]]
Отправлено: вторник, 9 июня 2015 г., 21:46
Кому: JuliaLang / julia
Копия: Сяо-Ган Вэнь
Тема: Re: [julia] Серьезное отношение к переносу векторных изображений (# 4774)

Я не уверен, что понимаю эти аргументы, но одна вещь привлекла мое внимание. Возможно, это принятие желаемого за действительное, но было бы хорошо, если бы компьютер мог думать так же быстро, как человеческий мозг. Я имею в виду, что когда Брайан Саттон говорит о программе «выбор предпочтительного представления», я представляю себе программу, которая может думать, как и мы, когда мы занимаемся математикой. Возможно, это невозможно с сегодняшними технологиями и слишком сильно замедлит работу. Но, как было бы хорошо ...

-
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

Вы спрашиваете, как в настоящее время работает slice . Я считаю, что A[stuff] станет синонимом slice(A, stuff) , и вы, вероятно, исполните свое желание.

Дорогой Тим:

Спасибо за совет. Я пробовал ломтик. Это не соответствует моим потребностям. Slice создает новый тип данных «подмассив», который я не могу использовать в другом моем коде, который использует тип данных :: Array.

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

Сяо-Ган


От: Тим Холи [[email protected]]
Отправлено: понедельник, 22 июня 2015 г., 17:32
Кому: JuliaLang / julia
Копия: Сяо-Ган Вэнь
Тема: Re: [julia] Серьезное отношение к переносу векторных изображений (# 4774)

Вы спрашиваете, как в настоящее время работает срез. Я считаю, что A [материал] станет синонимом slice (A, прочее), и поэтому вы, вероятно, получите свое желание.

-
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -114268796.

Есть ли что-то действительно зависящее от использования конкретных типов Array вместо AbstractArray в вашем коде? Для работы может потребоваться не что иное, как поиск / замена Array на AbstractArray .

Дорогой Скотт

Большое спасибо за подсказку.

Сяо-Ган


От: Скотт П. Джонс [[email protected]]
Отправлено: четверг, 25 июня 2015 г., 9:55
Кому: JuliaLang / julia
Копия: Сяо-Ган Вэнь
Тема: Re: [julia] Серьезное отношение к переносу векторных изображений (# 4774)

Есть ли что-то действительно зависящее от использования конкретных типов массивов вместо AbstractArray в вашем коде? Для работы может потребоваться не что иное, как поиск / замена Array на AbstractArray.

-
Ответьте на это письмо напрямую или просмотрите его на Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -115265047.

Это помогло тебе? Рад помочь!

@wenxgwen , в качестве альтернативы, вы можете вызвать copy(slice(A,...)) , чтобы получить копию среза в новом Array который затем должен работать изначально с вашими уже существующими функциями.

Исправление этой проблемы теперь стало прибыльным делом.

Поскольку решение этой проблемы сейчас имеет решающее значение на моем пути к 3 запятым ...
image

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

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

v --> length-2 Vector{Col}
  [ 1
    2 ]

v'  --> length-2 Vector{Row}
  [ 1 2 ]

m --> 2x2 Matrix{Col}
  [ 1 3 
    2 4 ]

m' --> 2x2 Matrix{Row}
  [ 1 2 
    3 4 ]

Some operations:
v'  --> length-2 Vector{Col}
v'' == v
v*v or v'*v'  --> either error, or do element-wise multiplication
v' * v --> scalar
v * v' --> 2x2 Matrix  (could be Row or Col??)
v' * m --> 2-length Vector{Row}
v * m --> either error or broadcasting operation
m * v --> either error or broadcasting operation
m * v' --> 2-length Vector{Col}

Indexing:
v[2] --> 2
v[1,2] --> error
v'[1,2] --> 2
m[1,2]  --> 3
m'[1,2]  --> 2

Size:
length(v)  --> 2
length(v')  --> 2
size(v)  --> (2,)
size(v')  --> (2,)
length(m)  --> 4
size(m)  --> (2,2)

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

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

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

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

Сравнение столбцов и строк не входит в сферу действия этой проблемы.

@tbreloff Мне

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

Если вам не нравится идея добавления черты строки / столбца в массивы, тогда
то же самое можно сделать с помощью TransposeView {T, N}, но я
подозреваю, что это будет сложнее реализовать хорошо.

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

В субботу, 26 сентября 2015 г., Скотт П. Джонс [email protected]
написал:

@tbreloff https://github.com/tbreloff Идея мне очень нравится. Это
было бы здорово иметь возможность более легко взаимодействовать с языками / библиотеками
которые являются строковыми.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -143436947.

Меняет ли вообще перегрузка вызовов пространство дизайна? Временами в приведенном выше значении * возникала некоторая двусмысленность. В частности, когда мы хотим, чтобы v::Covector * w::Vector возвращал скаляр, принимаем ли мы * самом деле как «map w под v » вместо умножения матриц? Если так, то нельзя ли аналогичным образом потребовать, чтобы w::Vector * v::Covector возвращал скаляр, поскольку векторы сами по себе являются линейными отображениями над ковекторами?

Возможно, вместо этого было бы полезно перегрузить call и написать v(w) чтобы обозначить операцию «сопоставить под v » для w , и аналогично для w(v) - оба вернут скаляры. Обеспечит ли это большую свободу действий в адаптации семантики, которая используется для операций с массивами и линейных алгебраических операций в случае двумерных массивов?

Если так, то нельзя ли аналогичным образом потребовать, чтобы w :: Vector * v :: Covector возвращал скаляр, поскольку векторы сами по себе являются линейными отображениями над ковекторами?

Я думаю, что мы пытаемся следовать матричным и векторным соглашениям, которые многие люди видят в университете на первом курсе, которые некоммутативны и имеют вектор * транспонированный вектор -> матрицу (ранга-1, т.е. имеющую только одну (ненулевую) сингулярную значение). То, что вы написали, больше похоже на концепцию абстрактной линейной алгебры, где вектор и ко / двойные векторы являются отображениями друг от друга на некоторый скаляр, что хорошо, но немного отличается от того, что (IMHO) предпринимается в Julia и MATLAB. Я думаю, что мы могли бы интерпретировать то, что вы написали, как внутренний продукт, dot() или действующий на два ковектора (которые мы, вероятно, _должны_ определить для ковекторов, я чувствую), но иногда нам также нужен внешний -product, и в настоящее время некуммутирующий * позволяет нам писать и выражать оба поведения.

Что касается соглашения о вызовах v(w) , мы также можем сказать, что для скалярного произведения нам нужна сумма v в направлении w что предполагает использование оператора индексации v[w] . (Кстати, я не говорю, что это хороший вариант языкового дизайна - просто наблюдение!)

в равной степени мы можем сказать, что для скалярного произведения нам нужна сумма v в направлении w что предполагает использование оператора индексации v[w] .

Это предложение почти соответствует семантике индексации, но не соответствует ей. Это также противоречит нашей текущей поддержке векторной индексации, если v - это Vector{<:Integer} .

Учтите, что для v :: Vector{T<:Real} , v[1] эквивалентно точечному произведению v⋅e₁ , где e₁ - это канонический базисный вектор вдоль первой оси. Поэтому простое индексирование - это действительно функция

   v[n] : n :: Integer --> y = (v ⋅ eₙ) :: T

Для векторной индексации v[[1, 2]] производит [v⋅e₁, v⋅e₂] что является результатом проецирования v в подпространство, охватываемое {e₁, e₂} , или, что эквивалентно, это результат v' * [e₁ e₂] .

Итак, векторная индексация - это функция

   v[I] : I :: Vector{<:Integer} --> y = v' * [eₙ for n in I] :: Vector{T}

Предложение сделать v[w] = (w ⋅ v) v несовместимо с этим определением, поскольку оно устраняет неявное отображение из набора индексов n (как указано w ) в набор канонических базисных векторов. eₙ , что необходимо для работы наших текущих правил индексирования.

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

Итак, я выстрелил в ковектора. Это большая работа, и, к сожалению, я не смогу уделять ей больше времени. Я размещаю его здесь в надежде, что кто-то либо побежит с ним, либо мы извлечем уроки из трудностей и решим сбежать. Но у меня есть здание ветки с транспонированием в виде представлений и умножением матриц, реализованным с помощью ковекторов. Некоторые выбранные тесты проходят успешно, но только без depwarn=error (например, ./julia -e 'using Base.Test; include("test/matmul.jl")' ). https://github.com/JuliaLang/julia/compare/mb/transpose

Я определил два новых типа представления, которые будут использоваться для transpose и ctranspose матриц и векторов:

immutable MatrixTranspose{C,T,A} <: AbstractArray{T,2}
    data::A # A <: AbstractMatrix{T}
end
immutable Covector{C,T,V} 
    data::V # V <: AbstractVector{T}
end

Параметр C - это простое логическое значение, указывающее, является ли транспонирование транспонированием или нет. Обратите внимание, что Covector _не_ подтип AbstractArray ; это довольно серьезное требование для разумной работы диспетчеризации.

Некоторые недостатки:

  • Ковекторы явно сложны, и их будет сложно документировать доступным и строгим образом. Здесь может помочь язык - мы могли бы просто назвать их RowVector s. Тем не менее, самая первая трудность, с которой вы сталкиваетесь при попытке объяснить такое поведение, - это то, как вы говорите о форме ковектора ( size не определено). Если (m,n) - это форма матрицы, тогда (m,) может представлять форму вектора ... и если мы злоупотребляем нотацией кортежей, мы можем на жаргоне описать ковектор, имеющий форму (,n) . Это позволяет нам выражать правила смешанной векторной / матричной алгебры с «отсутствующим» измерением, которое объединяет и распространяет несколько разумно:

    • Матрица * Вектор: (m,n) × (n,) → (m,)

    • Ковектор * Матрица (,m) × (m,n) → (,n)

    • Vector * Covector - это (n,) × (,n) → (n,n)

    • Ковектор * Вектор равен (,n) × (n,) → α (скаляр)

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

    • Мутация: (мутирующая, не мутирующая)
    • Транспонировать: (A, Aᵀ, Aᴴ) × (B, Bᵀ, Bᴴ).
    • Форма: (Мат × Мат; Vec × Мат; Мат × Vec, Vec × Vec). Обратите внимание, что не все из них поддерживаются во всех комбинациях транспонирования, но большинство из них.
    • Реализация: (BLAS, Strided, универсальный, плюс специализации для структурных матриц)

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


  • Они не сразу решают ни одну из трудностей с отбрасыванием всех скалярных измерений. Поддержка любого вида транспонирования вектора, когда мы отбрасываем скалярные измерения, ставит нас на неоднозначную территорию в отношении комплексного сопряжения (см. Https://github.com/JuliaLang/julia/pull/13612). Возможно, мы могли бы заставить A[1,:] возвращать ковектор, но это не очень хорошо обобщается, и было бы довольно странно возвращать не-AbstractArray из среза. Было бы здорово, если бы люди могли попробовать # 13612 и специально искать случаи, когда сопряженное транспонирование вектора вызывает проблемы - это было бы так же плохо с текущей семантикой транспонирования вектора и не требовало бы раскрытия Covectors.

Некоторые преимущества:

  • Использование диспетчеризации напрямую вместо специального синтаксического анализа для Ax_mul_Bx - огромный выигрыш. Он очень хорошо сочетается с API BLAS. В общем, вы хотите выполнять сопряжение на самых внутренних уровнях алгоритмов, поэтому имеет смысл хранить эту информацию вместе с аргументами. Для вызовов BLAS можно использовать простую функцию для поиска символа транспонирования ( ntc ).
  • Умножение между векторами и ковекторами теперь ассоциативно, потому что v'v возвращает скаляр. Теперь вы можете вычислить v'v*v слева направо и избежать формирования матрицы.
  • Это позволяет удалить вектор * Matrix, который работает только в том случае, если вы обрабатываете все векторы так, как если бы они имели конечные одиночные измерения… и это отличный шаг на пути к полному удалению поддержки конечных одиночных измерений в целом.

Прочие примечания:

  • Сложность транспонирования, представленная параметром логического типа, работает нормально, но мне часто казалось, что я определил параметры в неправильном порядке. Я редко действительно хотел ограничить или захватить и T и C . Я не уверен, что это было бы лучше с точки зрения количества типов-заполнителей, которые вам нужно было бы определить, но, по крайней мере, это будет соответствовать AbstractArray{T} .
  • Это действительно усугубляет трудности StridedArray. Прочитать таблицу методов для * довольно сложно с такими типами, как ::Union{DenseArray{T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},2},MatrixTranspose{C,T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},A<:Union{DenseArray{T,1},DenseArray{T,2},SubArray{T,1,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD},SubArray{T,2,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD}}},SubArray{T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},2,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD}} . Конечно, я написал это как typealias, но даже просто управлять всеми этими псевдонимами разных типов - это боль ( StridedMatOrTrans , QRCompactWYQorTranspose и т. Д.).
  • У меня не было возможности интегрировать это с SparseVector, но это будет огромная победа, поскольку он будет эффективно представлять транспонирование без необходимости CSR.
  • Нужно ли нам определять скалярную и / или нескалярную индексацию для ковекторов? Если да, то каков результат r[:] или r[1:end] ? Это вектор или ковектор? Думаю, я бы попытался уйти, не уточняя этого, если сможем. Интересно, что в Matlab есть очень особые правила для индексации векторов-строк с другими векторами - они очень стараются сохранить свою строку за счет некоторых странных угловых случаев ( r((1:end)') is r(1:end) is r(:)' ). На данный момент в моей ветке определена скалярная индексация, но, возможно, ее тоже следует удалить. Это дало бы понять, что Covector должен использоваться только с операциями линейной алгебры, которые знают о нем.

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

Из любопытства, если вы все еще помните @mbauman , что побудило вас ввести логический параметр C ? Разве ты не можешь просто (в псевдотрейтах)

transpose(::Matrix) -> MatrixTranspose
transpose(::MatrixTranspose) -> Matrix

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

По-видимому, это можно было бы обобщить до типа PermutedDimensionArray с параметром кортежа, кодирующим перестановку. Очевидно, что тип Covector имеет другое назначение и должен быть отдельной вещью.

Вам нужен способ обработки сопряженных транспозиций (а также неперемещенных сопряженных массивов для (A').' ). Я вижу три очевидных способа сделать это:

  • Сохраните спряжение как логическое поле только в одном типе MatrixTranspose . Поразмыслив, я думаю, что это определенно лучший вариант для взаимодействия с внешним BLAS. Однако с точки зрения встроенного JuliaBLAS я бы хотел убедиться, что Julia / LLVM может вывести T.isconjugate ? conj(T[i,j]) : T[i,j] из циклов.
  • Один тип с параметром сопряжения. Это то, что я выбрал, и я думаю, что на меня повлиял тот факт, что в настоящее время мы выполняем псевдо-рассылку о сопряженности посредством Ac_mul_Bt и друзей. Он также имеет более надежные гарантии относительно оптимизации удаления веток, которую может сделать Джулия. Но я не особо задумывался об этом ... Я просто хотел начать реализацию скетча, и меня больше беспокоил Ковектор.
  • Два отдельных типа: MatrixTranspose и MatrixCTranspose . Изоморфен параметру типа, но меня раздражают два разных типа оболочки. Абстрактные супертипы и псевдонимы объединения могут помочь, но я бы все равно выбрал параметр вместо этого варианта.

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

Интересно, могли бы мы иметь оболочку ConjugateView с правилом conj и transpose чтобы ConjugateView помещалось в оболочку MatrixTranspose . Т.е. для A a Matrix ,
A' = conj(transpose(A)) = transpose(conj(A)) все создают MatrixTranspose{ConjugateView{Matrix}} (отбрасывая неинформативные параметры типа).

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

Я рад, что вы все еще работаете над этим! Это эпическая ветка! Три ура !!!

Перекрестная ссылка https://github.com/JuliaLang/julia/pull/6837#issuecomment -213123832

Это планируется в версии 1.0?

В двух выпусках (№18056, №18136) мне указали на эту гигантскую ветку.
Так что я попробую.

Теперь у Джулии есть истинные одномерные векторы, _ например, mx[row,:] больше не является матрицей 1xn.
Это долгожданное изменение!

Но в качестве побочного эффекта некоторые существующие проблемы стали более очевидными.
Меня укусило то, что v*mx не работает для одномерных векторов.
Математически он должен работать естественно и возвращать 1-мерный вектор,
когда общий продукт a*b определяется контрактом
последний индекс первого члена и первый индекс второго члена.

В настоящее время сигнатура метода продукта Vector-Matrix:
(*)(A::AbstractVector, B::AbstractMatrix) = reshape(A,length(A),1)*B
и этот метод используется для случая v*v' а не для v*mx .
(Спасибо @andreasnoack за указание на это.)
Очевидно, что один метод не может использоваться для обоих.

Похоже, что Джулия борется с некоторыми ковенциями в стиле Matlab.
Но в Matlab нет одномерных векторов, только матрицы 1xn и nx1,
так много естественных вещей, что здесь может быть проблема.
У Джулии есть настоящие одномерные векторы, что должно быть большим преимуществом.
Было бы неплохо достичь действительно стабильного собственного состояния.

Фортран - лучший и более последовательный пример для подражания в этом случае.
Операция transpose определена только для матриц в Фортране,
создание матрицы 1xn из истинного 1-мерного вектора просто не является транспонированием.
Для matmul см. Отрывок из книги Меткалфа, цитируемый в # 18056.

Я думаю, что большинство первоначальных замечаний @alanedelman были правильными.

Итак, вот предложение, которое просто вылечит некоторые существующие проблемы,
максимально уважая текущее состояние:

  • оставить v' как для создания матрицы 1xn из истинного одномерного вектора v
  • Функции rowmx и colmx были бы лучше, но v' слишком распространены, чтобы их можно было изменить
  • у нас уже есть функция vec для создания истинного 1-мерного вектора
  • хотя обратное к v' не v'' а vec(v') , мы можем жить с этим
  • продукт a*b всегда должен сжимать последний индекс a и первый индекс b
  • оператор * не следует использовать ни для внутренних, ни для внешних продуктов.
  • для внутренних продуктов у нас уже есть функция dot
  • для внешних продуктов следует исключить использование * (_i.e._ синтаксис v*v' )
  • для внешних продуктов следует использовать новую функцию
  • соответствующий инфиксный оператор может облегчить синтаксис

Было бы неудачно потерять [краткий математический синтаксис] внешних продуктов. Я лучше лично откажусь от vec * mat.

Может ли существующий PernutedDimsArray уже обрабатывать случай, когда родительский объект и представление имеют разное количество измерений? Если да, то, возможно, это уже можно использовать в качестве обертки несопряженного транспонирования даже для родительских векторов.

Я не нашел PermutedDimsArray в документации.

Но я чувствую, что вместо того, чтобы изобретать еще больше типов данных,
следует четко разделять только три типа продуктов массива.
Внутренние продукты уже разделены.
Нам нужно только разделить обычные и внешние продукты.
Внешние продукты не будут потеряны, изменится только их синтаксис.
Пожалуйста, рассматривайте случай v*mx только как симптом более глубокой проблемы.

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

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

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

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

Хотя v*mx может показаться вам странным, он широко используется в кристаллографическом коде.
С ним также хорошо справляется matmul Фортрана. (См. №18056)

Вернемся к продукту u*v' .
Когда u и v обе являются матрицами размером nx1, это нормальное матричное произведение,
что дает тот же результат, что и внешний продукт.
Но это просто использование матричного продукта для имитации внешнего продукта.
Это безупречно в мире Matlab, где все представляет собой матрицу.

В Julia у нас есть настоящие одномерные векторы, которые ближе к миру Фортрана.
В Юлии транспонированный вектор v' уже является проблемой,
Фортран предпочел запретить это, как это уже предлагали Джулии другие.

Фортран не имеет внутренней функции для внешних продуктов.
Джулия легко преобразует v' в матрицу 1xn,
и выполняет операцию * над 1-мерным вектором и матрицей 1xn.
Благодаря многократной отправке он может это сделать,
но здесь * больше не является матричным произведением.

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

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

(О, я не учел перекрестное произведение, но оно уже хорошо разделено)

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

В условиях абстрактной линейной алгебры основными игроками являются векторы v (живущие в векторном пространстве V ), линейные карты (действующие в одном векторном пространстве V и отображение в возможно другое пространство W ) и состав этих отображений, линейных форм или ковекторов (живущих в двойном пространстве V* и отображения из V в скаляры), внутренние произведения (от V × V в скаляры), тензорные произведения между векторами (т.е. kron ). Внешний продукт я предпочитаю рассматривать как тензорное произведение между вектором и ковектором.

Особый интерес для этой проблемы представляет изоморфизм между векторами и линейными формами, если внутренний продукт определен, то есть для каждого f векторов отображения v в скаляры существует w так что f(v) = dot(w,v) для любых v . Однако ковекторы могут существовать без ссылки на внутренние продукты (например, градиент многомерной функции).

Чтобы представить все эти объекты на компьютере, вы обычно выбираете основу, а затем можете представить большинство из них с помощью матричной алгебры. То есть вам нужно только умножение и транспонирование матриц, если вы хотите быть гибкими с конечными одноэлементными измерениями (векторы как матрицы nx1, скаляры как матрицы 1x1 и т. Д.). Это была попытка Matlab, а также способ написания многих книг и статей, особенно по числовой линейной алгебре.

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

Из-за важности стабильности типов в коде Julia подход Matlab «только матрицы» (с гибкими конечными одноэлементными размерами) не работает. Но мы также не хотим переходить к полностью абстрактной настройке и продолжать работать в типичной обстановке (евклидовы внутренние продукты, тривиальное отображение векторов в ковекторы, ...). Поскольку в итоге мы пишем код и хотим использовать символы ASCII, нам нужно выжать как можно больше из символов * , ' и .' . К счастью, здесь может пригодиться множественная отправка, которая приводит к различным предложениям, изложенным выше. Я составил таблицу об этом, то есть о том, как абстрактные операции линейной алгебры будут преобразованы в конкретные методы julia (а не функции).

И последнее замечание к @GaborOszlanyi : я все еще не нахожу места для v*A во всем этом. Это может быть стандартным для полей, где векторы по умолчанию обозначаются как матрицы строк, но лично я думаю, что это странный выбор. Если линейные карты f и g действуют как f(v) = v*A и g(v) = v*B , то это означает, что g(f(v)) = (g ◦ f)(v) = v*A*B нечетное, поскольку порядок композиции меняется местами. Если есть, я мог бы интерпретировать это как неполный внутренний продукт, как в предпоследней строке связанной таблицы.

Ваше резюме глубокое и убедительное.
Спасибо, что так хорошо это объяснили.

У меня осталось всего два вопроса:

  • Какие изменения действительно произойдут в Юлии в результате этого обстоятельного обсуждения?
  • Почему Фортран реализовал v*mx в matmul ?

Эта проблема связана с двумя проблемами:

A. Прикладные математики используют нотацию Хаусхолдера, которая неявно использует два естественных изоморфизма:

  1. Вектор длины N неотличим от матрицы столбцов Nx1, поскольку матрицы Nx1 образуют векторное пространство. ("колонизировать", ▯)
  2. Матрица 1x1 неотличима от скалярного числа, поскольку матрицы 1x1 могут быть определены со всеми алгебраическими свойствами скаляров. ("скаляризовать", ■)

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

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


Меня все еще можно убедить в обратном, но вот мое текущее предложение из трех частей:

a) Полностью запретить транспонирование векторов, требуя от пользователей явного преобразования векторов в матрицы столбцов, чтобы писать выражения в стиле Хаусхолдера, такие как u'v , u*v' , u'*A*v и u'*A*v/u'v . Все эти выражения могут быть построены всего из трех элементарных унарных и бинарных операторов: matmul, транспонирование матрицы и деление матрицы. Напротив, если u и v являются истинными векторами, то подвыражения, такие как u' или u'*A означают без введения специального TransposedVector Тип

Ограничение (а) будет заключаться в том, что все выражения в стиле Хаусхолдера будут генерировать матрицы размером 1x1 вместо истинных скаляров (что по-прежнему является огромным улучшением по сравнению с u'v возвращающим 1-вектор), поэтому выражение типа (u'*v)*w все равно не сработает. На практике я не думаю, что подобные выражения «тройной продукт» встречаются часто.

б) Введите альтернативные обозначения для аналогичных операций над векторами, например

  • u ⋅ v = scalarize(columnify(u)'*columnify(v)) для внутреннего (точечного) произведения
  • u ⊗ v = columnify(u)*columnify(v)' для внешнего (кронекеровского) продукта
  • A(u, v) = scalarize(columnify(u)'*A*columnify(v)) , старое обозначение билинейной формы
  • A(u) = A(u, u) для квадратичной формы

Выражения в (b) отличаются от своих аналогов в (a) автоматической скаляризацией выражений для внутреннего произведения и билинейных / квадратичных форм, что позволяет избежать формирования матриц 1x1.

c) Сделайте матрицы 1x1 конвертируемыми в истинные скаляры, чтобы код вида

M = Array(Int, 1, 1, 1)
a::Int = M

может работать. Все, что потребуется, - это выполнить проверку размера массива во время выполнения, например:

function convert{T}(::Type{T}, A::Array{T,N})
    if length(A) == 1
        return A[1]
    else
        error()
    end
end

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

@jiahao , читабельность этого поста ограничена из-за труднопрорисовываемых символов. Не смотрится даже в OS X, в шрифтах которой обычно почти полностью используется Unicode.

@jiahao , я, конечно, мог бы согласиться с большинством / всем этим, хотя я хотел бы оспорить два момента:

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

Мне это кажется логическим следствием, если вы хотите сделать TransposedVector подтипом иерархии AbstractArray , что, как я предполагал, не входило в план (в отличие от некоторого типа LazyTranspose который действительно это просто еще один вид AbstractMatrix ).

u ⊗ v за внешний (кронекеровский) продукт

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

  • Произведение Кронекера A ⊗ B - это операция, определенная на матрицах, а не на векторах.
  • Следовательно, вместо того, чтобы читать u ⊗ v как тензорное произведение двух векторов, это привело бы к двумерному тензору типа «вниз вниз». Единственный способ получить правильную «матрицу» - это взять тензорное произведение вектора на ковектор. Но поскольку вы не хотите вводить эти объекты, кажется, что невозможно получить результат этой операции до колонки двух задействованных векторов.
  • Третье имя для u ⊗ v - это внешний продукт, который обычно определяется небрежно, и я не уверен, существует ли принятое строгое определение в терминах «вверх» и «вниз». Некоторые источники утверждают, что это эквивалентно тензорному произведению двух векторов, отсюда и предыдущий пункт. Если вместо этого вы принимаете определение, в котором «внешний продукт» также означает неявное сопоставление второго вектора с ковектором, чтобы получить тензор вниз-вверх, тогда проблем нет.

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

Мы рассматривали ядерный вариант? Мы начинаем линейную алгебру заново, полностью удаляя typealias для AbstractVector и AbstractMatrix и заменяя их на:

abstract AbstractVector{T} <: AbstractArray{T,1}
abstract AbstractMatrix{T} <: AbstractArray{T,2}

# and we could introduce:
abstract AbstractCoVector{T} <: AbstractArray{T,1}

Я понимаю, что есть много проблем, но в конечном итоге мы можем получить четкое разделение между многомерными массивами хранения и линейной алгеброй. Нам не нужно реализовывать полную многомерную тензорную алгебру, двойные векторные пространства и т. Д. Нам просто нужно реализовать биты, которые нужны многим людям: векторы, ковекторы и матрицы. Мы получаем внутренние продукты, внешние продукты, ковекторно-матричные продукты, матричные-векторные произведения и матрично-матричные произведения. Транспонирование определяется всем вышеперечисленным. У нас могут быть реализации AbstractMatrix которые действительно используют одномерный массив одномерных массивов (конечно, не реализация по умолчанию). Нам не нужно использовать нотацию Хаусхолдера (что, IMHO, является одной из слабых сторон MATLAB!), Но мы все равно можем получить все удобства линейной алгебры.

Я немного нервничаю, предлагая это, но я считаю, что это позволит Джулии имитировать «правильную» модель того, что большинство людей изучает, например, на первом курсе линейной алгебры университетского уровня, без необходимости возвращаться к соглашениям Хаусхолдера. Проведение четкого различия может также упростить перенос Base.LinAlg в пакет «стандартной библиотеки», что, как я считаю, является долгосрочной целью для Джулии? Это также хорошо согласуется с идеей, что будет новая вещь, похожая на список 1D с изменяемым размером, которая будет идти с новыми изменениями Buffer и собственной реализацией Array , так что этот общий тип "списка" может заменить Vector для многих основных частей Julia и позволить нам загружать пакет LinAlg довольно поздно, в то время как Array и "список" определены довольно рано.

Существует множество алгоритмов, которые уже были "упрощены" и выражены в терминах массивов Фортрана, а не в терминах правильной линейной алгебры. Джулии необходимо иметь возможность реализовать эти алгоритмы без необходимости заново открывать (или принуждать) структуру линейной алгебры поверх многомерных массивов.

В моей работе, вероятно, лучше всего подходит правильная линейная алгебра, которая отображает тензоры, ко / контравариантные индексы и т. Д. На массивы Fortran. Чтобы это работало - и не сбивать людей с толку - я бы оставил в покое термины «вектор», «матрица» и «массив», оставив их на низком уровне, и использовал другие (более причудливые?) Термины для всего более высокого уровня. . Вышеупомянутый более высокий уровень должен также абстрактные варианты хранения, либо через абстрактные типы, либо через параметризацию. Возможно, префикс LA - это способ выразить это:

LA.Vector
LA.CoVector
LA.Tensor{... describing co/contravariance of indices ...}

Затем векторы и матрицы используются исключительно для хранения. На низком уровне транспонирование выполняется вручную (как в BLAS); на высоком уровне это выполняется автоматически.

Это близко к предложению @andyferris , за исключением того, что оно не нарушает обратную совместимость и не нарушает ожиданий Matlab / Fortran / numpy convert.

@eschnett Я думаю, что ранее в этой теме было решено оставить многолинейную алгебру для пакетов, а не для базы Julia. Я думаю, что многие из этих идей, как вы предлагаете, можно было бы конкретизировать в новом пакете, который обрабатывает тензоры, векторные пространства, ко / контр-дисперсию и так далее в системе типов, что несколько отличается от пакета _TensorOperations.jl_, который предоставляет удобные функции для умножения массивов, как если бы они были тензорами. Я думаю, что это будет сложно разработать, но потенциально стоящая абстракция!

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

Обратите внимание, что общее неевклидово комплексное векторное пространство V имеет 4 связанных пространства: V , conj(V) , dual(V) и conj(dual(V)) . Таким образом, общий тензор имеет 4 вида индексов (обычно обозначаемых как вверх или вниз, с перемычкой или без нее). В сложном евклидовом пространстве (например, квантовая механика) dual(V) ≡ conj(V) и conj(dual(V)) = V . В реальном (неевклидовом) пространстве (например, общая теория относительности) V ≡ conj(V) . В обоих последних случаях нужны только индексы вверх и вниз.

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

  • контракт / внутренний продукт , который можно обобщить на произвольные массивы ранга N используя правило: свяжите последний индекс первого массива с первым индексом второго (или numpy dot , хотя я считаю это менее интуитивным), так что A ∙ B возвращает массив ранга M+N-2 если A и B имели ранг M и ранг N .
  • тензорное произведение : A ⊗ B возвращает массив ранга N+M

Специализированные до векторов v , w и матриц A , B , это позволяет записывать все следующее:

  • векторный внутренний продукт v ∙ w -> в исключительных случаях возвращает скаляр вместо массива ранга 0
  • умножение матрицы на вектор A ∙ v
  • матрица умножение матриц A ∙ B
  • тензор / внешнее произведение v ⊗ w
  • ковекторное (=== векторное) умножение матриц v ∙ A

Хотя можно использовать * для , но ошибка состоит в том, что приведенное выше определение не ассоциативно: (A ∙ v) ∙ w ≠ A ∙ (v ∙ w) поскольку последнее даже не быть определенным.

Но опять же, это только в том случае, если вы готовы игнорировать сложные массивы.

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

Интересный подход, @Jutho. Кажется достаточно интуитивно понятным. Я _gessing_, что эти определения могут быть добавлены независимо от того, что мы делаем с * и ' , и я бы поддержал это.

Но опять же, это только в том случае, если вы готовы игнорировать сложные массивы.

conj() вставленный вручную, исправляет это. И я не думаю, что Юля хочет игнорировать сложные матрицы, не так ли?

Я заметил еще пару вещей о том, что AbstractMatrix является специальным подтипом, а не псевдонимом типа:

matrix[:,i] -> AbstractVector{T}
matrix[i,:] -> AbstractCoVector{T}
array_2d[:,i] -> AbstractArray{T,1}
array_2d[i,:] -> AbstractArray{T,1}

Это довольно круто - мы можем получить векторы столбцов и строк из индексации матрицы. Если это просто двухмерный контейнер для хранения, мы получаем одномерный массив хранения. Должен охватывать все варианты использования! И он по-прежнему подчиняется интерфейсу AbstractArray с правилами разделения APL, поскольку и AbstractVector{T} <: AbstractArray{T,1} и AbstractCoVector{T} <: AbstractArray{T,1} .

Один "интересный" факт:

array_3d[:,:,i] -> AbstractArray{T,2}
matrix(array_3d[:,:,i]) -> `Абстрактная матрица {T}

поэтому вам придется вручную обернуть его как матрицу, если вы хотите выполнить матричное умножение с результатом. Это плюс или минус, не знаю? Но это, похоже, одно из возможных осложнений и затрагивает то, что @eschnett упомянул об упрощении для пользователей других языков, которые объединяют хранение и линейную алгебру.

Это может быть глупый вопрос, но @jutho ранее упоминал, что написание кода ограничено ASCII. Почему мы все еще ограничиваемся 7-битным набором символов, разработанным в 1963 году и последний раз обновленным в 1986 году (30 лет назад)? Это была эпоха, когда знаменитые 640 КБ были максимальным объемом ОЗУ, доступным на ПК в 1981 году. На сегодняшнем компьютерном рынке сейчас в продаже обычно есть 64-разрядные процессоры с 32 ГБ ОЗУ (в 50 000 раз больше предыдущего максимума), и мы даже близко к этому не приблизились. теоретический предел для 64-битных процессоров. Итак, почему мы все еще ограничиваемся набором символов, разработанным 40 лет назад?

Мы можем использовать юникод, просто имейте в виду, что, к сожалению, размер клавиатуры не увеличился в аналогичных условиях за последние 40 лет, равно как и количество пальцев, которые есть у нормального человека (за последние 10000+ лет, на самом деле).

ИМХО, ASCII надо упокоить. Специальная математическая клавиатура для быстрого программирования математических символов - действительно хорошая идея! Есть ли веская причина не использовать больше символов, поставляемых с UTF? Можете ли вы оправдать попытки выжать из ASCII все, что только возможно?

@ esd100 : Пожалуйста, не используйте этот выпуск GitHub для столь спекулятивных обсуждений.

@Johnmyleswhite. Я не уверен, что вы имеете в виду под «спекулятивным». Вы уверены, что хотели использовать это слово? Я думал, что использование ASCII вместо чего-то еще имеет отношение к обсуждению, поскольку кажется, что язык, операторы и функциональность привязаны к используемым символам. Я ни в коем случае не эксперт, но это похоже на проблему с обсуждением или, по крайней мере, ясностью в отношении рассматриваемой функциональности.

Я имею в виду, есть ли причина, по которой мы не можем определить язык так, как мы хотим (имея в виду основные цели: простота использования, скорость)? Разве использование специальных символов / слов не сделало бы язык более богатым и красивым?

@ esd100, обратите внимание, что, например, последний комментарий @Jutho (с использованием двух символов Unicode) был хорошо принят, и @yuyichao соглашается, что мы можем использовать Unicode. Есть и другие предложения о введении большего количества операторов только для Юникода (например, символ компоновки для составления функций, # 17184). Я не думаю, что люди не согласны с вами (хотя у нас есть практические соображения, которые нужно иметь в виду), но если у вас есть _конкретные_ предложения по теме, сообщите нам об этом.

Меня немного смущает это новое поведение в v0.5 которое, как мне кажется, возникло в результате этого обсуждения:

julia> [ x for x in 1:4 ]' # this is fine
1×4 Array{Int64,2}:
 1  2  3  4

julia> [ Symbol(x) for x in 1:4 ]' # bit this isn't? What is special about symbols?
WARNING: the no-op `transpose` fallback is deprecated, and no more specific
`transpose` method for Symbol exists. Consider `permutedims(x, [2, 1])` or writing
a specific `transpose(x::Symbol)` method if appropriate.

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

Вы можете использовать изменение формы: reshape(v, 1, length(v)) . Может быть, это тоже следует упомянуть в предупреждении об устаревании? Я думаю, что идея состоит в том, что транспонирование - это математическая операция, и поэтому ее следует определять только для математических векторов / матриц.

Это упоминается в депварне: permutedims(x, [2, 1]) .

permutedims не работает с векторами. См. Этот новый выпуск: # 18320

Я думаю, что идея состоит в том, что транспонирование - это математическая операция, и поэтому ее следует определять только для математических векторов / матриц.

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

Я думаю, что идея состоит в том, что транспонирование - это математическая операция, и поэтому ее следует определять только для математических векторов / матриц.

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

Я думаю, что довольно сложно удовлетворить то, что вы хотите _и_ имеет смысл для математики. В математическом смысле транспонирование часто определяется заменой векторных пространств и их двойных пространств вектора или матрицы (ну, также есть некоторые сложности с транспонированием и сопряженным транспонированием). Для матрицы регулярных чисел M мы имеем транспонирование как permutedims(M, (2,1)) , но в целом вы можете определить, например, «блочную» матрицу в терминах подматриц, например:

M = [A B;
     C D]

где A т.д. - сами матрицы. Насколько я понимаю, Джулии нравится, когда

M' = [A' C';
      B' D']

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

Это означает, что элементы матрицы должны принимать ' и .' . Для чисел они определяются как комплексное сопряжение и запрет операций соответственно. ИМХО, я думаю, что это удобная игра слов, но она работает и, что наиболее важно, реализована с _simple_ правилами («транспонирование рекурсивно» - в отличие от необходимости BlockMatrix классов и так далее). Но этот каламбур на транспонировании был удален из нечисловых типов в 0.5, потому что он не имеет особого смысла. Что означает транспонирование Symbol ?

Если у вас есть _data_, а не числа, то использование permutedims прекрасно определено по своему значению и поведению: оно нерекурсивно. Использование математического каламбура, такого как .' может сэкономить вам несколько символов при вводе, но это будет иметь _lot_ больше смысла для кого-то другого (который может быть не очень знаком с математикой или MATLAB), чтобы прочитать ваш код, если вы используете reshape и permutedims мере необходимости. _Для меня это_ самый _важный момент_.

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

Спасибо за вдумчивый ответ. Я все еще сильно не согласен

Но этот каламбур на транспонировании был удален из нечисловых типов в 0.5, потому что он не имеет особого смысла. Что такое транспонирование символа?

Я не уверен, что определение транспонирования Symbol как бездействия имеет меньший смысл, чем определение транспонирования для действительного числа. Я понимаю, что "каламбур" удобен, но кажется, что "правильнее" было бы сделать транспонирование, определенное только для векторов и матриц, и иметь transpose(A::Matrix{Complex}) apply conj как часть его исполнение.

Использование математического каламбура, такого как .' может сэкономить вам несколько символов при вводе, но для кого-то другого (который может быть не очень знаком с математикой или MATLAB) будет гораздо больше смысла читать ваш код, если вы используете измените форму и переставьте изображения по мере необходимости. Для меня это самый важный момент.

Я согласен с тем, что .' следует использовать разумно и что более явный вызов transpose часто бывает лучше. Я думаю, что reshape и permutedims могут потребовать нетривиального количества когнитивных накладных расходов, прежде всего, для чтения и кодирования. Честно говоря, это быстрее разбирается:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

Даже в таких простых случаях вам нужно отскочить от начала строки (чтобы прочитать reshape ) до конца (чтобы прочитать length(A) ), чтобы понять, что происходит. (Затем вы, вероятно, вернетесь к середине, чтобы понять, почему length(A) вообще существует.)

Для меня большая проблема в том, что если я новая Джулия (что означает, что я, вероятно, раньше использовал numpy и MATLAB), и я вижу, что это работает:

[ x for x = 1:10 ]'

Я, естественно, предполагаю, что то же самое будет работать для массивов строк, символов и т. Д. Я не собираюсь читать долгие онлайн-дискуссии, чтобы выяснить семантику .' - я собираюсь попробовать кое-что и обобщить / сделать вывод из прошлого опыта. Возможно, здесь поможет более подробное сообщение об ошибке.

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

Но сохранение бездействия кажется безобидным.

Вы не можете реально определить что-либо значимое для Any потому что все является подтипом Any . Определение transpose(x)=x совершенно неверно для любого вида матрицы, и, чтобы избежать некоторых скрытых ошибок, нам пришлось добавить эти определения . Таким образом, это компромисс между удобством использования относительно странного синтаксиса ' для совершенно нематематической операции и избежанием скрытых ошибок.

Я не уверен, что определение транспонирования символа как бездействия имеет меньший смысл, чем определение транспонирования для действительного числа. Я понимаю, что "каламбур" удобен, но кажется, что "правильным" было бы иметь транспонирование, определенное только для векторов и матриц, и иметь transpose(A::Matrix{Complex}) apply conj как часть его исполнение.

Я не полностью согласен, но нам нужно будет реализовать какой-то BlockMatrix или специальные методы, определенные для Matrix{M<:Matrix} . Я не уверен, популярна ли эта идея или нет? (Это серьезный вопрос для тех, кто следит за ним, поскольку он может упростить некоторые из этих связанных вопросов).

Честно говоря, это быстрее разбирается:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

Второе, потому что я не имею отношения к текущему транспонированию векторов Джулии (очевидно, _Я тоже принимаю векторные транспозиции_ _серьезно_ :)) Если бы мне пришлось сделать второе, я бы подробно написал rowvec = reshape(colvec, (1, n)) или, возможно, просто [f(x) for _ = 1:1, x = 1:n] чтобы заставить понимание начать правильную форму, или если вам действительно нравится .' тогда map(f, (1:n).') и f.((1:n).') настоящее время тоже работают.

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

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

нам потребуется реализовать какую-то BlockMatrix или определить специальные методы для Matrix {M <: Matrix}

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

[f (x) для _ = 1: 1, x = 1: n]

Я забыл об этом! Вероятно, этим я и займусь. В целом, я все еще не согласен с вашими вкусами к читабельному коду, но каждому свое! ¯\_(ツ)_/¯

Ясно, что я слишком серьезно отношусь к переносу векторных изображений

Да. Я думаю так. 😉

Это (https://github.com/JuliaLang/julia/issues/16790) также сделало бы использование reshape вместо transpose немного более приемлемым для меня.

Возможно, я ошибаюсь, но я думаю, вам просто нужно сделать второе (изменить: специальные методы транспонирования определены для Matrix{M<:Matrix} ), что мне кажется разумным вариантом.

К сожалению, теперь мы снова возвращаемся к различию между данными и линейной алгеброй. Вы хотите, чтобы блочные матрицы линейной алгебры имели рекурсивное транспонирование, но общие массивы 2D-данных массивов 2D-данных не имели рекурсивного транспонирования ... но пока Matrix{T} и Array{T,2} - это то же самое, что мы не можем сделай это.

Это (# 16790) также сделало бы использование reshape вместо транспонирования немного более приемлемым для меня.

Правда!!

Вы хотите, чтобы блочные матрицы линейной алгебры имели рекурсивное транспонирование, но общие массивы 2D-данных массивов 2D-данных не имели рекурсивного транспонирования

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

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

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

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

Если бы они были, то изменение размера (с помощью push! ) или даже значений объектов линейной алгебры не поддерживалось бы (и такие пользователи использовали бы StaticArrays.jl и т.д.), а broadcast только поддерживаться на объектах линейной алгебры.
Объекты данных будут модифицируемыми, расширяемыми и будут поддерживать map , (и reduce и filter ), но не broadcast .

Но мы не живем в мире, где люди либо думают двоично об объекте данных, либо об объектах линейной алгебры.
Таким образом, это 2,5 года, 340 веток комментариев.


Повторите транспонирование no-op .
Мы могли бы добавить очень высоко в иерархию типов абстрактные типы Scalar и Nonscalar .
Scalars все откат к безоперационному транспонированию.
Nonscalars имеют выхода (но сейчас вернемся к предупреждению об устаревании)

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

Массивы (матрицы, векторы и другие) будут подтипами Nonscalar и будут иметь рекурсивное транспонирование.

Я не уверен, нравится мне это или нет.

Недавняя волна сообщений перефразирует дискуссии о _matrix transpose_ и "скалярных" типах в # 18320 # 13171 # 13157 # 8974.

Я действительно хотел бы развеять представление о том, что резервный вариант transpose(x)=x no-op безвреден. На мой взгляд, откат по-прежнему должен давать правильный результат, только медленнее, чем оптимизированный алгоритм. Опасно, когда резервный метод молча вычисляет неправильный ответ, потому что это означает, что откат не имеет правильной семантики: transpose(x) означает разные вещи в зависимости от типа x .

Откат без операции транспонирования неверен не только для матриц матриц, но и для всех матричных объектов, которые не являются подтипами AbstractMatrix (что на # 987 означает, что они явно сохраняют записи, _not_ что у них есть алгебра матриц). Вот один пример дочернего плаката (которого у нас довольно много):

julia> A = rand(5,5); F = qrfact(A); R = F[:R]; Q = F[:Q] #Q is an example of a matrix-like object
5x5 Base.LinAlg.QRCompactWYQ{Float64,Array{Float64,2}}:
 -0.518817    0.0315127   0.749223    0.410014  -0.0197446
 -0.613422   -0.16763    -0.609716    0.33472   -0.3344   
 -0.0675866   0.686142    0.0724006  -0.302066  -0.654336 
 -0.582362   -0.0570904   0.010695   -0.735632   0.341065 
 -0.104062    0.704881   -0.248103    0.295724   0.585923 

julia> norm(A - Q*R) #Check an identity of the QR factorization
8.576118402884728e-16

julia> norm(Q'A - R) #Q'A is actually an Ac_mul_B in disguise
8.516860792899701e-16

julia> Base.ctranspose(Q::Base.LinAlg.QRCompactWYQ)=Q; #Reintroduce no-op fallback

julia> norm(ctranspose(Q)*A - R) #silently wrong 
4.554067975428161

Этот пример показывает, что то, что что-то является подтипом Any , не означает, что вы можете предположить, что это скаляр и у него нет операции транспонирования. Это также показывает, почему так сложно решить родительскую проблему в OP. Для матричного объекта Q, не являющегося массивом, Q' не имеет реального значения как операция с массивом, но имеет однозначное алгебраическое значение: такие выражения, как Q'A , совершенно четко определены. Другие люди, которые работают с массивами, а не с линейной алгеброй, просто хотят нерекурсивных перестановок и не заботятся о матричных не массивах. Тем не менее, факт остается фактом: вы не можете иметь единообразное написание как алгебраической семантики, так и семантики замены осей для всех типов.

Может быть, я тупица, но я подумал, что сравнение будет таким:

julia> A = rand(5,5); F = qrfact(A); R = F[:R]; Q = F[:Q]
julia> Base.ctranspose(Q::Any) = Q;
WARNING: Method definition ctranspose(Any) in module Base at operators.jl:300 overwritten in module Main at REPL[6]:1.
julia> norm(ctranspose(Q)*A - R) # still works fine
4.369698239720409e-16

Перезапись transpose таким образом, похоже, позволяет transpose([ :x _=1:4 ]) - то есть то, о чем я писал. Я бы подумал, что пока вы правильно реализуете transpose / ctranspose для всего, что в этом нуждается (например, QRCompactWYQ ), резервный вариант никогда не будет вызван (поскольку более конкретный вызов может быть изготовлен).

Ваш код не вызывает написанный вами метод ctranspose (вы можете проверить это с помощью @which ). Он вызывает другой резервный метод в Julia v0.5 (которого нет в v0.4), который, по сути, выполняет ctranspose(full(Q)) . Этот другой запасной вариант верен, но устраняет саму причину, по которой у нас есть этот причудливый Q-тип (так что умножение на него может быть выполнено точно). Мой комментарий о том, что резервные копии должны быть правильными, все еще в силе.

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

Спасибо @toivoh, что заставило меня щелкнуть. Я очень ценю объяснения.

Но если вы определите резервные функции transpose(X::AbstractMatrix) и transpose(X::AbstractVector) то, вероятно, вы всегда получите правильный результат (даже если он будет медленным ... например, вызовом full ) нет? И вы всегда можете написать более специализированную функцию, чтобы делать это лучше / быстрее. Тогда transpose(::Any) никогда не должен вызывать тихих ошибок, кроме упомянутых ранее "каламбуров" (т.е. при обработке чисел Complex ... может быть, другие скалярные варианты использования, о которых я не знаю?)

По историческим причинам QRCompactWYQ <: AbstractMatrix , но согласно # 987 # 10064 это отношение подтипов неверно и должно быть удалено.

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

Мы должны продолжить это обсуждение в https://github.com/JuliaLang/julia/issues/13171. Проблема в основном связана с векторами с числовыми элементами.

Кто-то из «командной линейной алгебры» должен сделать шаг вперед и взять на себя обязательство что-то сделать с этим для версии 0.6, иначе он снова столкнется с проблемой.

Итак, каков план перезагрузки?

Мои мысли приводят меня к следующему: transpose(v::AbstractVector) = TransposedVector(v) где TransposedVector <: AbstractVector . Единственная семантическая вещь, которая будет отличать TransposedVector от AbstractVector , - это его поведение при * (и всех A_mul_B s, \ , / , ...). Т.е. это декоратор, определяющий, какие индексы нужно сокращать до * (и т. Д.). Транспонирование будет концепцией линейной алгебры, и если вы хотите переставить массив «данных», следует поощрять reshape и permutedims .

Единственная семантическая вещь, которая будет отличать TransposedVector от AbstractVector , - это его поведение при *

Итак, v'==v но v'*v != v*v' ? Хотя это может иметь смысл, это также может сбивать с толку.

Итак, v'==v но v'*v != v*v' ? Хотя это может иметь смысл, это также может сбивать с толку.

ИМО, это неизбежно (хотя, возможно, и прискорбно).

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

Если мы не отделим линейную алгебру от массивов (например, сделаем Matrix{T} как подтип, так и неизменяемую оболочку Array{T,2} , с определенными другими (специфичными для линейной алгебры) методами), тогда я не уверен это большой выбор, который согласуется как с его свойствами массива, так и с линейной алгеброй.

Один непонятный вопрос (для меня) заключается в том, транслируется ли он как вектор или в своем втором измерении. Если его размер равен (1, n) тогда это изменение в целом мало что делает, кроме утверждения его как матрицы, в которой первое измерение, как известно, имеет длину 1 . Транспонирование Vector{T} должно остаться Array{T,2} (то есть Matrix ...), однако транспонирование этого может снова быть Vector ( например, у нас может быть v'' === v ).

Это лучшая идея? Это было бы меньше ломки и все же улучшило бы семантику по отношению к линейной алгебре. РЕДАКТИРОВАТЬ: и он ведет себя по-разному относительно == поскольку @martinholters вызывает).

Для меня наличие TransposedVector{T <: AbstractVector{Tv}} <: AbstractMatrix{Tv} *) с size(::TransposedVector, 1)==1 но transpose(::TransposedVector{T})::T звучит как самый разумный подход, но было так много споров, что, вероятно, есть хороший аргумент против этого?

*) Я знаю, что это синтаксически неверно, но надеюсь, что предполагаемая семантика ясна.

Да, играя с идеями в коде, я согласен с вами, @martinholters.

Я начал с https://github.com/andyferris/TransposedVectors.jl. Все это может быть достигнуто с помощью лишь небольшого количества пиратства типов и переопределения методов из пакета за пределами базы, и я сделал пакет совместимым с Julia 0.5. Если все получится, может, портируем на Base? (Или еще выучите уроки).

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

Я бы не стал использовать отдельный CTransposedVector , потому что он объединяет две концепции (конъюгацию и изменение формы) в один объект. Гораздо удобнее реализовать эти две концепции как отдельные объекты. Например, с MappedArrays.jl это так же просто, как

conjview(A) = mappedarray((conj,conj), A)

и вы получите отличную производительность.

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

Одна прекрасная вещь заключается в том, что отдельное изменение, которое по умолчанию устанавливает conj на conjview будет зависеть от этого изменения (я думаю, что с ними можно было бы возиться в независимых пакетах, и они правильно составили бы вместе). Есть ли желание заработать conj за просмотр? Мы ждем, когда inline non-isbits inmutables сделает такие представления бесплатными? Или не нужно ждать?

@andyferris : Я думаю, что ваш подход хорош - спасибо, что продвинули его вперед. Если реализация удалась, мы можем просто использовать этот код в Base. Следует иметь в виду, что нам также может понадобиться TransposedMatrix для https://github.com/JuliaLang/julia/issues/5332. За пределами 1 и 2 измерений я не думаю, что транспонирование имеет смысл, поэтому это конечный набор типов.

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

Прямо сейчас асимметрия заключается в том, что x' продвигает x в матрицу, тогда как A*x не продвигает x в матрицу. Если бы операции линейной алгебры были только для 2D, тогда A*x должно было продвигаться, а x'x было бы матрицей 1x1 . В качестве альтернативы мы могли бы позволить векторам быть векторами и, следовательно, также A*x быть вектором, но тогда x' должно быть ошибкой или ошибкой. Введение транспонированного вектора потребует множества новых определений методов, и, похоже, единственное преимущество состоит в том, что x'x становится скаляром.

Я думаю, что транспонирование матрицы совсем другое. Это позволит нам избавиться от всех методов Ax_mul_Bx и их поведение не является предметом обсуждения, как поведение векторных транспонированных объектов.

Я действительно не понимаю, как введение типа TransposedVector вызывает больше проблем, чем введение типа TransposedMatrix.

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

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

Ошибка при переносе вектора похожа на выброс ребенка вместе с водой из ванны. Невозможность написать v' для транспонирования вектора будет действительно очень раздражать. Я действительно не понимаю, что такого плохого в наличии типа TransposedVector, который ведет себя как матрица-строка, за исключением того, как он умножается на векторы и матрицы.

Это зависит от того, сколько вариантов поведения вы хотите исправить с помощью транспонирования вектора. Если вы просто хотите v'' == v , тогда это нормально для typeof(v') <: AbstractMatrix . Но если вам нужны другие вещи, о которых мы говорили в этой цепочке, например typeof(v'v) <: Scalar или typeof(v'A) <: AbstractVector , тогда это должно быть другое, более сложное чудовище.

Идея сделать TransposedVector своего рода вектором, кажется, лежит в основе многих проблем. Похоже, что было бы гораздо менее разрушительно, если бы она была своего рода матрицей и имела бы тогда size(v.') == (1,length(v)) как сейчас. Единственная разница в том, что двойное транспонирование возвращает вектор, а v'v дает скаляр. Если кто-то хочет рассматривать транспонирование как вектор, можно, так как length(v') дает правильный ответ, а линейная индексация работает, как ожидалось.

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

Подход, при котором он имеет размер (1, n) означает, что с точки зрения поведения Array он ведет себя точно так же, как и сейчас. _Only_ операции, которые будут отличать его от 1 на N Matrix - это поведение при ' , .' , * , \ . и / .

Ни один из этих операторов не является «массивом». Они предназначены исключительно для реализации линейной алгебры между матрицами и векторами и исходят непосредственно из MATLAB («лаборатория матриц»). Это последнее уточнение просто говорит, что size(tvec, 1) = 1 статически для компилятора _и_, что я хочу, чтобы v'' было v . (Я думаю, что это немного похоже на StaticArray где одно измерение фиксировано, а другое имеет динамический размер).

Если вы просто хотите v '' == v, тогда это нормально для typeof (v ') <: AbstractMatrix.

Правильно.

Но если вам нужны другие вещи, о которых мы говорили в этой ветке, например typeof (v'v) <: Scalar или typeof (v'A) <: AbstractVector, тогда это должно быть другое, более сложное чудовище.

Почему? Мы не нарушаем никаких его свойств, подобных массиву, поэтому он все еще может быть массивом. Любой точечный вызов будет работать так же, как и раньше, а другие векторизованные операции будут удалены. Я думаю, что * "специализируется" на знании различных форм Vector и Matrix и это потому, что мы пытаемся имитировать поведение письменной линейной алгебры. Сделать v' * v скаляром - это _очень_ стандартная математика, и я считаю, что единственная причина, по которой это не было так с самого начала, заключается в том, что это нетривиально реализовать последовательным образом (и вдохновение Джулии черпает из MATLAB), но Стефан мог бы уточнить это. Преобразование внутреннего продукта в скаляр - единственное критическое изменение, о котором можно здесь говорить (есть и другие очень незначительные вещи) - я не понимаю, почему одно это сделало бы его непригодным для использования в качестве Array (существует много типов из Array которые не имеют действительных ' , .' , * , \ и / определены _все_ , заметно ранг 3+)

Одна из проблем, с которой я столкнулся в прошлом году, заключалась в двусмысленности; IIRC, их было намного проще решить, когда вектор транспонирования не был массивом.

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

В общем, было бы замечательно, если бы мы сделали Base менее монолитным и включили его в стандартные библиотеки. Наличие автономного пакета или модуля, который просто определяет AbstractArray и Array , упростит разработку такого рода.

В чем именно заключалась проблема с предложением @Jutho ? Не может * автоматически (сопряженно) переставить вектор слева?

Если у нас есть * - матричное умножение, ' - матричное (сопряженное) транспонирование (оставляет векторы без изменений) и два оператора promote которые добавляют конечный синглтон и demote который удаляет завершающий синглтон, тогда мы можем создать правое приложение *: (n,m) -> (m,) -> (n,) , левое приложение *: (n,) -> (n,m) -> (m,) , скалярное произведение *: (n,) -> (m,) -> (,) и внешний продукт ×: (n,) -> (m,) -> (n,m) такое, что:

(*)(A::AbstractMatrix, v::AbstractVector) = demote(A*promote(v))
(*)(u::AbstractVector, A::AbstractMatrix) = demote((promote(u)'*A)')
(*)(u::AbstractVector, v::AbstractVector) = demote(demote(promote(u)'*promote(v)))
(×)(u::AbstractVector, v::AbstractVector) = promote(u)*promote(v)'

Я не уверен, что мне когда-нибудь понадобится транспонировать вектор с помощью этих операторов. Я что-то упускаю?

Изменить: не совсем предложение @Jutho .

@Armavica , я не совсем уверен, что это было именно мое предложение присвоить это значение * а скорее новому (unicode) оператору , который в настоящее время эквивалентен dot . Более того, я не думаю, что вы сможете реализовать подход promote и demote стабильно по типу.

Пурист во мне согласен с @andreasnoack, что следует избегать транспонирования самого вектора (я лично предпочитаю писать dot(v,w) вместо v'w или v'*w любое время), но я также могу оценить гибкость, позволяющая заставить его работать способом, предложенным

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

(Я лично предпочитаю писать точку (v, w) вместо v'w или v '* w в любое время)

Юто - с другой стороны, держу пари, вы пишете не | ψ> ⋅ | ψ> на листе бумаги или белой доске, а скорее <ψ | ψ> (и та же аналогия применима к тому, как мы рисуем внутренние продукты с тензорными сетевыми диаграммами).

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

Теперь вы заставляете меня отвлечься, чего я больше не собирался делать. Я, конечно, согласен предпочитать <ψ | ψ> для внутреннего продукта, но это не зависит от того факта, что я не думаю о <ψ | как эрмитово сопряжение | ψ>. Для операторов определено эрмитово сопряжение. Естественный изоморфизм между векторами и связанными с ними двойственными векторами (ковекторами, линейными функционалами и т. Д.) Известен как теорема о представлении Рисса , хотя первая, конечно, эквивалентна эрмитовскому сопряжению при интерпретации векторов длины n как линейных отображений. от C до C ^ n, т.е. как матрицы размера (n,1) .

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

Смешно

Теперь вы заставляете меня отвлечься, чего я больше не собирался делать.

Извините ... я действительно не должен был упоминать об этом ... :)

Я не думаю о <ψ | как эрмитово сопряжение | ψ>. Для операторов определено эрмитово сопряжение.

Правда, я об этом не подумал.

Естественный изоморфизм между векторами и связанными с ними двойственными векторами (ковекторами, линейными функционалами, ...) известен как теорема о представлении Рисса, хотя первая, конечно, эквивалентна эрмитовскому сопряжению при интерпретации векторов длины n как линейных отображений из C в C ^ n, т.е. как матрицы размера (n, 1).

Я стараюсь не отвлекаться (но у меня это плохо получается), но я думаю, что мы получили немного этого последнего, поскольку мы связываем AbstractVector с 1D Array . Другими словами, AbstractVector не означает «абстрактный вектор» в математическом смысле, но вместо этого означает «числовые элементы вектора относительно некоторого предопределенного базиса». MATLAB легко выбрался из этого, потому что векторы имели размер (n,1) .

В качестве пищи для размышлений, что вы называете (антилинейным) оператором, который принимает все тензоры вида |a>|b><c| и отображает их в |c><a|<b| ? Я всегда объединял это вместе с векторным двойственным и стандартным оператором сопряжения как «эрмитово сопряжение», но, возможно, это слишком преувеличенно.

У меня был разговор с @alanedelman, в котором

  • введите Covector или RowVector type;
  • для линейных алгебраических операций ( * , ' , .' , / , \ , может быть norm ? другие? ) этот тип действует как ковектор в линейной алгебре;
  • для операций с массивами (всего остального) этот тип действует как 2-мерный массив 1 × n;
  • в частности, ковектор size(v') равен (1, length(v)) .

Этот подход требует классификации всех общих функций в Base как линейных алгебраических или нет. В частности, size является функцией массива и не имеет смысла в области линейной алгебры, которая, по сути, имеет только векторы (которые не имеют "формы", только количество размеров), линейные преобразования (которые могут быть представлены двумерными массивами) и скалярами. Одним из конкретных примеров, который возник, был cumsum , который в настоящее время работает с двумерными массивами, как если бы был предоставлен аргумент измерения 1. Это несовместимо с sum , которое вместо аргумента измерения по умолчанию, равного 1, возвращает сумму всего массива. Это также предотвращает работу cumsum с ковекторами в соответствии с тем, как это работает с векторами. Я думаю, что cumsum должен работать с общими массивами путем кумулятивного суммирования в N-мерном порядке по столбцам. В более общем случае аргументы измерения не должны по умолчанию равняться 1.

Я был бы немного обеспокоен, если бы не было интерфейса для определения ковекторов. Большинство операций, например size и ndims, говорят, что они такие же, как и другие массивы 2d или 1d. Только попробовав, например, скалярное произведение, вы увидите разницу. AFAICT вам нужно будет проверить, является ли объект RowVector, но очень редко требуется, чтобы объект имел определенный тип, чтобы иметь определенное общее свойство.

@StefanKarpinski Я согласен со всем этим. Тем более, что функции являются либо операциями «массива», либо операциями «линейной алгебры».

Я начал с size = (1, n) TransposedVector здесь, что в точности соответствует вашим словам. Мне потребовалось время, чтобы получить хорошее покрытие тестов и всех комбинаций * , \ , / для каждого возможного c и t метод и методы изменения, избегая двусмысленности с другими методами в base. Это медленная, но систематическая работа, и я подумал, что мы могли бы вытащить ее на базу, когда она будет готова (возможно, переименование вещей).

Их отличие от Covector где это действительно должно быть сопряженное транспонирование (или даже более общее преобразование, в общем случае!), В то время как RowVector или TransposedVector концептуально проще.

@JeffBezanson Есть ли что-то, что мы можем сделать с indices() чтобы иметь "одноэлементное" измерение?

но очень редко требуется, чтобы объект имел определенный тип, чтобы иметь определенное общее свойство.

Да, было бы круто, если бы это была черта характера или что-то в этом роде. Я надеялся, что мы сможем отделить линейную алгебру от массивов в более общем смысле, но это огромное изменение. и, вероятно, не мог бы быть изящным без красивого синтаксиса признаков, реализованного в Julia. Я думаю, здесь проблема в том, что мы сопоставляем 3 вещи (векторы, ковекторы и матрицы) на два типа ( AbstractArray{1} и AbstractArray{2} ), и поэтому один из них (ковекторы) становится особым подтипом. другого.

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

@JeffBezanson : Я не понимаю твоего беспокойства. Идея состоит в том, что он ведет себя так же, как абстрактный массив 1 × n 2d, за исключением операций линейной алгебры, для которых он ведет себя как двойное пространство векторов-столбцов (которое изоморфно абстрактным массивам 1 × n 2d). Если вы хотите проверить ковектор, вы можете проверить тип, например, отправив по нему.

Обновленная информация о моих попытках решить эту проблему:

TransposedVectors.jl теперь, я считаю, "завершен". Он содержит все механизмы, необходимые для выполнения того, о чем здесь говорил @StefanKarpinski - транспонирование вектора - это оболочка вектора, который ведет себя как двумерный абстрактный массив размером 1xn, - но для операций линейной алгебры ( * , / , \ , ' , .' и norm ) он ведет себя как вектор-строка (или двойственный вектор).

Проверить это можно так:

julia> Pkg.clone("https://github.com/andyferris/TransposedVectors.jl")
...

julia> using TransposedVectors
WARNING: Method definition transpose(AbstractArray{T<:Any, 1}) in module Base at arraymath.jl:416 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/TransposedVector.jl:28.
WARNING: Method definition ctranspose(AbstractArray{#T<:Any, 1}) in module Base at arraymath.jl:417 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/TransposedVector.jl:29.
WARNING: Method definition *(AbstractArray{T<:Any, 1}, AbstractArray{T<:Any, 2}) in module LinAlg at linalg/matmul.jl:86 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:9.
WARNING: Method definition At_mul_B(AbstractArray{#T<:Real, 1}, AbstractArray{#T<:Real, 1}) in module LinAlg at linalg/matmul.jl:74 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:37.
WARNING: Method definition Ac_mul_B(AbstractArray{T<:Any, 1}, AbstractArray{T<:Any, 1}) in module LinAlg at linalg/matmul.jl:73 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:64.

julia> v = [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> vt = v'
1×3 TransposedVectors.TransposedVector{Int64,Array{Int64,1}}:
 1  2  3

julia> vt*v
14

julia> vt*eye(3)
1×3 TransposedVectors.TransposedVector{Float64,Array{Float64,1}}:
 1.0  2.0  3.0

Может быть некоторое снижение производительности - тестирование будет полезно. В некоторых случаях он будет делать меньше копий (транспонирование является ленивым), а в других случаях он делает больше копий (например, иногда сопряженная копия вектора (никогда не матрица) создается в Ac_mul_Bc ). И сама оболочка имеет небольшую стоимость, пока мы не встраиваем изменяемые поля в неизменяемые (к счастью для меня, это уже хорошо работает со StaticArrays.jl : smile :). Также существует проблема проверки того, что это какой-то StridedArray когда это необходимо.

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

Неужели люди думают, что такой PR будет иметь смысл в v0.6 без типа оболочки для транспонированных матриц? В то время как транспонированные векторы представляют собой семантическое изменение, транспонированные матрицы - это скорее оптимизация, поэтому я полагаю, что они могут быть включены отдельно. Конечно, может показаться "странным", если некоторые транспозиции являются представлениями, а некоторые - копиями - мнениями? Еще один момент, который следует учитывать, заключается в том, что после того, как транспонированные матрицы и векторы будут вставлены, потребуется проделать большую работу по удалению всех функций At_mul_Bt , замененных методами на * , для завершения переход (хотя упрощение будет полезным!) - я сомневаюсь, что кто-то сможет или захочет сделать все это до конца этого месяца ...

Отличная работа, @andyferris! Вы вообще экспериментировали с универсальной ленивой оболочкой Conjugate ?

@andyferris Я пнул шины, и мне это очень нравится. Кажется, это строго улучшение, и я надеюсь, что проблемы с производительностью будут решены по мере их обнаружения. Посмотрим, что говорят другие.

Спасибо ребята :)

Вы вообще экспериментировали с универсальной ленивой оболочкой Conjugate ?

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

  • AbstractVector
  • Conjugate{V where V <: AbstractVector} (или Conj{V} , может быть, даже ConjArray ? Конечно, он принимает массивы любой размерности)
  • TransposedVector (или RowVector ?)
  • TransposedVector{Conjugate{V where V <: AbstractVector}}
  • AbstractMatrix
  • Conjugate{M where M <: AbstractMatrix}
  • TransposedMatrix (или просто Transpose{M} ?)
  • TransposedMatrix{Conjugate{M where M<:AbstractMatrix}}

(плюс все указанные черты базового хранилища, такие как то, что мы теперь называем DenseArray и StridedArray - я надеюсь, что # 18457 может немного облегчить их выражение).

Не считая наименования, звучит ли это как разумный план?

Что касается немедленного сброса велосипедов, то для PR для базы того, что в настоящее время в пакете, мы предпочитаем TransposedVector , RowVector , общую оболочку Transpose как для векторов, так и для матриц , или что-то другое?

Я думаю, что поведение, обеспечиваемое реализацией @andyferris , довольно хорошее и является улучшением. Но позвольте мне немного лучше выразить свою озабоченность.

Проблема заключается в определении новых типов массивов. Например, DArray - это подтип AbstractArray , поэтому DArray также может быть AbstractVector или AbstractMatrix . В идеале мы бы просто расширили различие вектор / матрица до строки / столбца / матрицы, так что DArray может быть AbstractRow , AbstractCol или AbstractMatrix . Иерархия типов будет примерно такой:

AbstractArray
    AbstractVector
        AbstractRowVector
        AbstractColumnVector
    AbstractMatrix
    ...

Вчера говорил об этом с

Здесь может быть некоторый приоритет, поскольку не все AbstractVector s могут использоваться в качестве столбцов, например:

julia> isa(1.0:10.0, AbstractVector)
true

julia> randn(10,10) * 1.0:10.0
ERROR: MethodError: no method matching colon(::Array{Float64,2}, ::Float64)

который может решить проблемы, которые у меня были в https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215

То есть просто отсутствуют круглые скобки вокруг диапазона?

Это просто проблема приоритета:

julia> randn(10,10) * (1.0:10.0)
10-element Array{Float64,1}:
 -22.4311
  ⋮

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

ах, хорошее замечание. Хотелось бы, чтобы неправильный приоритет пробелов был синтаксической ошибкой, похожей на Fortress.

Группа MIT указала, что один из способов реализовать описанную выше иерархию типов - это добавить параметр типа в иерархию массивов:

abstract AbstractArray{T,N,row}

type Array{T,N,row} <: AbstractArray{T,N,row}
end

typealias AbstractVector{T} AbstractArray{T,1}
typealias AbstractRowVector{T} AbstractArray{T,1,true}
typealias AbstractColVector{T} AbstractArray{T,1,false}
typealias AbstractMatrix{T} AbstractMatrix{T,2}

typealias Vector{T} Array{T,1,false}
typealias Matrix{T} Array{T,2,false}

Я не проработал все последствия этого - вероятно, хуже всего то, что Array{Int,1} становится абстрактным типом - но, похоже, это дает почти правильную иерархию типов и необходимые абстракции.

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

abstract AbstractArray{T,N,row}

type Array{T,N} <: AbstractArray{T,N}
end

isrow{T,N,row}(::AbstractArray{T,N,row}) = row
isrow{T,N}(::AbstractArray{T,N}) = false

julia> isrow(Array{Int,2}())
false

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

Мне это кажется равносильным определению

type Array{T,N} <: AbstractArray{T,N,false}
end

Что нам здесь может понадобиться, так это какие-то «параметры типа по умолчанию», такие, что создание типа Array{X,Y} где X и Y не имеют свободных переменных, фактически дает Array{X,Y,false} .

Другая идея: сохранить нотацию Хаусхолдера для внутренних продуктов v'v и умножить левый ковектор v'A , сделав инфикс ' собственным оператором вместо того, чтобы разбирать их как v'*v и v'*A . В сочетании с созданием v' Идентичности, v*v ошибки и v*A ошибки, это может дать много желаемым. Вам нужно будет написать внешние продукты как outer(v,v) .

В такой схеме v' было бы ошибкой или даже ошибкой - поскольку нет причин транспонировать вектор в такой схеме, это будет иметь смысл только для матрицы.

@JeffBezanson Я согласен, что AbstractRowVector (но я, честно говоря, не могу придумать вариант использования, поэтому я не реализовал его в пакете). Я также хотел бы отметить, что это может существовать как подтип AbstractMatrix . Причина, по которой я двинулся в этом направлении, заключается в том, что broadcast кажется Джулии более важным понятием, чем линейная алгебра, и люди будут ожидать, что вектор-строка будет, ну, ну, строкой!

Конечно, наличие RowVector <: AbstractMatrix - неудачное использование терминологии! Я думаю, это связано с тем, что двумерные массивы и абстрактные матрицы имеют одно и то же имя.

Я уже говорил об этом раньше, намного выше, но, поскольку эта проблема слишком длинная, я повторю ее: в универсальном языке, таком как Julia, свойство «массив данных» должно быть основным соображением для AbstractArray . Ответ на вопрос, как вы хотите, чтобы broadcast вел себя для «транспонированного» вектора, скажет вам, является ли он 1D или 2D. Если вы хотите мыслить в терминах строк и столбцов, тогда лучше всего подходят векторы 1 x n row. Если вы хотите рассматривать двойные векторы, то лучше всего подходит 1D - и мне это тоже нравится! Но взятие двойственного вектора сложнее, чем изменение формы данных (например, мы должны хотя бы поддерживать сопряжение).

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

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

Поскольку мы обсуждаем возможные деревья типов - в будущем, с Buffer и изменяемыми «списками», я представляю себе абстрактное дерево интерфейсов, которое будет что-то вроде

AbstractArray{T,N} # interface includes broadcast, Cartesian, getindex, setindex!, etc.
    AbstractArray{T,1}
        AbstractList{T} # resizeable, overloaded with `push!` and so-on
        AbstractVector{T} # non-resizeable, overloaded for *, /, \, ', .', etc
    AbstractArray{T,2}
        AbstractRowVector{T} # non-resizeable, overloaded for *, /, \, ', .', etc
        AbstractMatrix{T} # non-resizeable, overloaded for *, /, \, ', .', etc

Конечно, мы также могли бы иметь AbstractDualVector{T} <: AbstractArray{T,1} вместо AbstractRowVector .

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

То, что AbstractVector одновременно является вектором C ++ std::vector и вектором линейной алгебры, мне показалось немного дерзким :) (тем более, что интерфейс массива означает, что он никогда не может быть действительно "абстрактным вектором" в смысл линейной алгебры (например, без базиса))

Да, было бы хорошо отделить поведение изменения размера. Я согласен с этим.

Эта иерархия типов, похоже, подразумевает, что у нас будут отдельные конкретные типы «матрица» и «2-мерный массив» в Base. Мы могли бы попытаться обойти это, например, заставив конструктор для Array{T,2} действительно возвращать какой-то другой тип матрицы, но это кажется довольно некрасивым. Может, я что-то не понимаю.

Эта иерархия типов, похоже, подразумевает, что у нас будут отдельные конкретные типы «матрица» и «2-мерный массив» в Base.

Верно ... Я думаю, чтобы сделать это правильно, нам понадобятся черты характера. Имейте в виду, я назвал это «абстрактным деревом интерфейсов». Попытка сделать это в данный момент потребует чего-то уродливого, как вы сказали. Но мы могли бы, вероятно, вставить AbstractList <: AbstractVector (и переместить связанные методы), как только Buffer будет выполнено.

На данный момент связь между поддерживаемыми интерфейсами и деревом типов довольно слабая. При отправке трудно определить, является ли (абстрактный) массив изменяемым (т.е. поддерживает ли он setindex! ), можно ли изменять его размер, работает ли срезание только для типов, определенных в Base и т. Д. пользователям сложно предоставить функцию с методами, которые работают и эффективны с широким диапазоном входных данных (это мой опыт из StaticArrays ).

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

Одно соглашение, которое мы могли бы использовать, делает обычную практику использования абстрактного типа, отличного от Any, для создания онтопологической области, среди которой сопутствующие конкретные типы легко находят общий отдых. Это принесло бы за очень небольшую плату большую мульти-контекстную восприимчивость - понимание какой-то части иерархии типов помогает подходить к другим частям.

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

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

(например: RowVector лучшее имя, чем TransposedVector ? [1 2 3] RowVector или Matrix ? Что такое indices(row, 1) ?)

+1 для RowVector

20 декабря 2016 г. в 7:01 «Энди Феррис» [email protected] написал:

Хорошо, мы будем благодарны за любые дополнительные комментарии по TransposedVectors . я чувствую
он становится достаточно прочным и готовым к превращению в пиар, но есть
некоторые важные проблемы, которые я перечислил здесь
https://github.com/andyferris/TransposedVectors.jl/issues .

(например: RowVector - лучшее имя, чем TransposedVector? [1 2
3] RowVector или Matrix? Что такое индексы (строка, 1)?)

-
Вы получили это, потому что оставили комментарий.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment-268170323 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AAm20YYqsXmprI23GgI5PYyWStpTOq5qks5rJ309gaJpZM4BMOXs
.

Я действительно хотел бы исключить из него соглашение о строках / столбцах, и я думаю, что это звучит странно, когда есть Vector и RowVector (или даже ColVector и RowVector ). +1 за TransposedVector или DualVector

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

Имя TransposedVector в порядке. Но это немного долго. Кроме того, это вроде как предполагает, что объект этого типа может быть получен только путем транспонирования Vector . Но я полагаю, вы могли бы получить TransposedVector и другими способами, скажем, извлекая одну строку матрицы?

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

DualVector тоже неплохо, но CoVector звучит менее формально.

Связь с общественностью, которой я угрожал ранее, теперь подана на # 19670. Я пошел с RowVector пока.

Но я полагаю, вы могли бы создать TransposedVector и другими способами, например, извлекая одну строку матрицы?

На самом деле это один камень преткновения - я еще не придумал для этого отличный синтаксис. Есть идеи?

Но я полагаю, вы могли бы создать TransposedVector и другими способами, например, извлекая одну строку матрицы?

На самом деле это один камень преткновения - я еще не придумал для этого отличный синтаксис. Есть идеи?

Хотя сначала казалось привлекательным, чтобы matrix[scalar,range] (и другие подобные конструкции) давали вектор-строку, это было бы значительным отклонением от текущей семантики индексирования для многомерных массивов, а особые случаи вызывают у меня подозрение.

Вместо этого я хотел бы, чтобы RowVectorVector на то пошло) преобразуют любой итеративный тип в вектор соответствующего типа. Затем вы могли бы сделать что-то вроде RowVector(matrix[scalar,range]) что было бы достаточно ясно и не нарушало бы текущее поведение индексации массива.

Правильно, i th строка может быть извлечена в виде строки с помощью A[i,:].' , в настоящее время в версии 0.5, и будет продолжать делать это в будущем (чтобы получить RowVector или Transpose{V<:AbstractVector} или то , что мы в конечном итоге оседают на (см здесь для текущей дискуссии)). Может быть, это ответ.

А как насчет добавления двух новых функций в Base?
row(A,i) и col(A,i)
Последнее не нужно, а просто для симметрии. Он лаконичен, понятен и содержит столько символов, сколько A[i,:].'

@benninkrs, что имеет смысл. Напротив, моя интуитивная интерпретация - это интерпретация линейной алгебры, где вектор вообще не имеет ориентации. Все точки зрения на этот счет разумны, мне просто не нравятся Vector и RowVector вместе, потому что именование выглядит так, как будто оно смешивает абстрактную и конкретную парадигмы.

На данный момент, я думаю, нам нужно либо пойти на что-то, основанное на реализации @andyferris , либо решить не принимать всерьез перенос векторов. В качестве примера альтернативы, вот подход, который лечит наихудшие симптомы: оставьте v' виде, в котором оно есть в настоящее время, т.е. создание матрицы строк, но проанализируйте a'b как инфиксный оператор с приоритетом чуть ниже умножение. Другими словами, у нас будет следующее поведение:

  1. v' - матрица-строка (ctranspose)
  2. v'v - скаляр (скалярное произведение)
  3. v'*v - это 1-элементный вектор (матрица-строка * вектор)
  4. v*v' - матрица внешнего продукта (
  5. v'A - матрица-строка ("vecmat")
  6. v'*A - матрица-строка (matmat)
  7. v'A*v - скаляр (matvec A * v, затем скалярное произведение)
  8. (v'A)*v - это 1-элементный вектор (vecmat, затем matvec)
  9. v'*A*v - это 1-элементный вектор (matmat, затем matvec)
  10. v'' - матрица-столбец (вектор ctranspose, затем матрица ctranspose)

В текущей настройке 2 и 3 эквивалентны, а 7, 8 и 9 эквивалентны, что нарушает это изменение. Но самое главное, жирные элементы - это те, к которым люди обычно обращаются, поскольку они самые короткие и удобные из аналогичных форм - и все они делают то, что мы от них хотим. Никаких новых типов, только один новый инфиксный оператор. Главный недостаток - 10 - v'' по-прежнему матрица столбцов. Возможно, это можно рассматривать как преимущество, поскольку постфикс '' - это оператор для преобразования вектора в матрицу столбцов.

Сделав шаг назад, я думаю, что мы узнали, что без дополнительных функций маркировки вверх-вниз или размеров или без обработки ≤ 2 измерений как взаимозаменяемых, как это делает Matlab, многомерные массивы не могут быть гладко согласованы с линейной алгеброй. Итак, что у нас осталось, это вопрос удобства - можем ли мы позволить людям удобно выражать обычные операции линейной алгебры над векторами и матрицами без чрезмерного усложнения массивов? Я не совсем настроен на этот подход, но я думаю, что мы должны серьезно рассмотреть этот и другие подходы, которые обращаются к синтаксическому удобству, не слишком портя нашу иерархию типов массивов.

Другой подход - это специально проанализировать инфикс a'b (чуть ниже * ) и заставить v' вернуть сопряженный вектор. В более общем смысле, постфикс A' может сопрягать массив и лениво менять его размеры, в то время как A.' просто лениво меняет размеры массива, действуя как тождество векторов. В этой схеме список свойств может быть следующим:

  1. v' - вектор (сопряженный)
  2. v'v - скаляр (скалярное произведение)
  3. v'*v - ошибка (рекомендуется v'v для внутреннего продукта и outer(v,v) для внешнего продукта) †
  4. v*v' - ошибка (рекомендуется v'v для внутреннего продукта и outer(v,v) для внешнего продукта) †
  5. v'A - вектор (vecmat)
  6. v'*A - ошибка (рекомендуется v'A для vecmat)
  7. v'A*v - скаляр (matvec A * v, затем скалярное произведение)
  8. (v'A)*v - ошибка (рекомендуется v'v для внутреннего продукта и outer(v,v) для внешнего продукта) †
  9. v'A'v - скаляр ( v'(A'v) - сопряженное matvec, затем внутреннее произведение)
  10. v'' - вектор ( v'' === v и v.' === v )

Теперь, когда я выписываю все эти свойства, это может быть предпочтительным вариантом: все случаи ошибок на самом деле служат, чтобы помочь людям обнаружить и использовать определенные синтаксисы, и, конечно, у него есть желаемое свойство v'' === v (и красиво ласточкин хвост где .' является универсальным оператором изменения размерности). Наличие очень похожих синтаксисов, которые лишь немного отличаются друг от друга, вероятно, сбивает с толку.

† Мы потенциально можем обнаружить их во время синтаксического анализа для более точных ошибок с риском выдачи ошибок в случаях, когда код пользователя перегружает ' и * чтобы эти операции работали. Я считаю, что для исправления этих рекомендаций может потребоваться ленивая оболочка конъюгатов, но нам это все равно нужно для # 5332.

Сделав шаг назад, я думаю, что мы узнали, что без дополнительных функций маркировки вверх-вниз или размеров или без обработки ≤ 2 измерений как взаимозаменяемых, как это делает Matlab, многомерные массивы не могут быть гладко согласованы с линейной алгеброй. Итак, что у нас осталось, это вопрос удобства - можем ли мы позволить людям удобно выражать обычные операции линейной алгебры над векторами и матрицами без чрезмерного усложнения массивов? Я не совсем настроен на этот подход, но я думаю, что мы должны серьезно рассмотреть этот и другие подходы, которые обращаются к синтаксическому удобству, не слишком портя нашу иерархию типов массивов.

: 100:

Явное выполнение постфиксных операций ' и .' универсальным массивом (а не с линейной алгеброй) позволяет избежать удвоения при объединении типов хранения и линейной алгебры и оставляет дверь открытой для фреймворков, предполагающих меньшее количество компромиссов. Между тем, способность этих операций имитировать нотацию Хаусхолдера в наиболее распространенных случаях должна обеспечивать наибольшее желаемое удобство. Также меньше кода и сложности. Лучший!

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

На данный момент, я думаю, нам нужно либо пойти на что-то, основанное на реализации @andyferris , либо решить не принимать всерьез перенос векторов.

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

  • IMO, несколько более разумные типы линейной алгебры (мы не отрицаем существование двойных векторов, как сейчас, хотя вектор-строку можно рассматривать как своего рода частный случай двойного вектора)
  • v'' === v
  • Некоторые вещи в списке Стефана, например v1'v2 являются точечным произведением.
  • Семантика массива почти обратно совместима - например, результат size(v') не изменился, но у нас v'' как одномерный
  • Ленивые объединение и транспонирование оберток могут повысить производительность в определенных обстоятельствах и в любом случае будут полезны
  • Удаление всех функций Ac_mul_Bc в пользу * и A_mul_B! (аналогично для \ , / ).

Честно говоря, заставить все это работать за Array не потребуется слишком много усилий (для меня проблема заключается в том, чтобы найти время в это время года и отыскать все другие типы, которые есть в нашем наборе линейной алгебры). И последний пункт - вздох облегчения.

С другой стороны - ИМХО эти правила, кажется, немного усложняют синтаксический анализ и могут немного сбивать с толку и / или удивлять, как они составляют ' с * (3, 4, 6 и 8 будут работать с RowVector ).

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

19670 выглядит либо готовым, либо близким к нему, если есть желание что-то внедрить в v0.6.

БАМ

Woot.

Это была наша самая длинная тема?

Нет, # 11004 есть еще

Сожалею. Вы правы, я должен был указать ветку открытых вопросов.

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

Смежные вопросы

sbromberger picture sbromberger  ·  3Комментарии

manor picture manor  ·  3Комментарии

musm picture musm  ·  3Комментарии

iamed2 picture iamed2  ·  3Комментарии

TotalVerb picture TotalVerb  ·  3Комментарии