Julia: Серьезное отношение к транспонированию матрицы

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

В настоящее время transpose рекурсивен. Это довольно неинтуитивно и приводит к несчастью:

julia> A = [randstring(3) for i=1:3, j=1:4]
3×4 Array{String,2}:
 "J00"  "oaT"  "JGS"  "Gjs"
 "Ad9"  "vkM"  "QAF"  "UBF"
 "RSa"  "znD"  "WxF"  "0kV"

julia> A.'
ERROR: MethodError: no method matching transpose(::String)
Closest candidates are:
  transpose(::BitArray{2}) at linalg/bitarray.jl:265
  transpose(::Number) at number.jl:100
  transpose(::RowVector{T,CV} where CV<:(ConjArray{T,1,V} where V<:(AbstractArray{T,1} where T) where T) where T) at linalg/rowvector.jl:80
  ...
Stacktrace:
 [1] transpose_f!(::Base.#transpose, ::Array{String,2}, ::Array{String,2}) at ./linalg/transpose.jl:54
 [2] transpose(::Array{String,2}) at ./linalg/transpose.jl:121

Уже некоторое время мы говорим людям вместо этого делать permutedims(A, (2,1)) . Но я думаю, что в глубине души мы все знаем, что это ужасно. как мы сюда попали? Что ж, довольно хорошо понятно, что нужно, чтобы ctranspose или «сопряженная» матрица матриц была рекурсивной. Хорошим примером является то, что вы можете представлять комплексные числа с помощью матриц 2x2 , и в этом случае «сопряжение» каждого «элемента» (на самом деле матрицы) является его сопряженным в виде матрицы - другими словами, если ctranspose рекурсивен, тогда все работает. Это всего лишь пример, но он обобщает.

Рассуждения, кажется, были следующие:

  1. ctranspose должно быть рекурсивным
  2. ctranspose == conj ∘ transpose == conj ∘ transpose
  3. transpose следовательно, также должно быть рекурсивным

Думаю, здесь есть несколько проблем:

  • Нет причин, по которым ctranspose == conj ∘ transpose == conj ∘ transpose должен удерживаться, хотя название делает это почти неизбежным.
  • Поведение conj работающее поэлементно на массивах, является своего рода неудачным пережитком Matlab и на самом деле не является математически оправданной операцией, так же как exp поэлементное управление не совсем математически обосновано и что expm does было бы лучшим определением.
  • Фактически он принимает сопряженный (т. Е. Сопряженный) каждый элемент, что означает, что ctranspose должно быть рекурсивным; в отсутствие конъюгации нет веских причин для рекурсии транспонирования.

Соответственно, я бы предложил следующие изменения для исправления ситуации:

  1. Переименуйте ctranspose (он же ' ) в adjoint - это действительно то, что делает эта операция, и это освобождает нас от того, что она должна быть эквивалентна conj ∘ transpose .
  2. Устарела векторизация conj(A) для массивов в пользу conj.(A) .
  3. Добавьте аргумент ключевого слова recur::Bool=true к adjoint (урожденный ctranspose ), указывающий, должен ли он вызывать себя рекурсивно. По умолчанию это так.
  4. Добавьте аргумент ключевого слова recur::Bool=false к transpose указывающий, должен ли он вызывать себя рекурсивно. По умолчанию это не так.

Как минимум, это позволило бы нам написать следующее:

julia> A.'
4×3 Array{String,2}:
 "J00"  "Ad9"  "RSa"
 "oaT"  "vkM"  "znD"
 "JGS"  "QAF"  "WxF"
 "Gjs"  "UBF"  "0kV"

Сможем мы или нет сократить это до A' зависит от того, что мы хотим сделать с conj и adjoint нечисловых (или, более конкретно, нереальных, нечисловых). -сложные значения).

[Этот выпуск является вторым из серии статей о ω₁.]

breaking decision linear algebra

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

(ОТ: Я уже с нетерпением жду «Серьезно относиться к 7-тензорам», следующей части очень успешного мини-сериала из 6 частей ...)

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

Логический преемник предыдущего выпуска ... 👍

Поведение сопряженной операции поэлементно с массивами является своего рода неудачным пережитком от Matlab и на самом деле не является математически оправданной операцией.

Это совсем не так и совсем не аналог exp . Комплексные векторные пространства и их сопряжения - это хорошо известная математическая концепция. См. Также https://github.com/JuliaLang/julia/pull/19996#issuecomment -272312876

Комплексные векторные пространства и их сопряжение - это хорошо известная математическая концепция.

Если я не ошибаюсь, правильная операция математического сопряжения в этом контексте - это ctranspose а не conj (что было моей точкой зрения):

julia> v = rand(3) + rand(3)*im
3-element Array{Complex{Float64},1}:
 0.0647959+0.289528im
  0.420534+0.338313im
  0.690841+0.150667im

julia> v'v
0.879291582684847 + 0.0im

julia> conj(v)*v
ERROR: DimensionMismatch("Cannot multiply two vectors")
Stacktrace:
 [1] *(::Array{Complex{Float64},1}, ::Array{Complex{Float64},1}) at ./linalg/rowvector.jl:180

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

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

@StefanKarpinski , вы ошибаетесь. Вы можете иметь комплексное сопряжение в векторном пространстве без сопряженных элементов - сопряжения - это концепция, которая требует гильбертова пространства и т. Д., А не только комплексного векторного пространства.

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

(Комплексное сопряжение не означает, что вы можете умножать conj(v)*v !)

Прекращение использования векторизованных conj не зависит от остальной части предложения. Не могли бы вы дать ссылки на определение комплексного сопряжения в векторном пространстве?

https://en.wikipedia.org/wiki/Complexification#Complex_conjugation

(Это довольно формальный подход; но если вы погуглите "комплексно сопряженную матрицу" или "комплексно сопряженный вектор", вы найдете множество вариантов использования.)

Если отображение комплексного вектора в сопряженный вектор (что сейчас делает conj ) является важной операцией, отличной от отображения его в сопряженный ковектор (что делает ' ), то мы, безусловно, можем сохранить conj чтобы выразить эту операцию с векторами (и матрицами, и более высокими массивами, я думаю). Это обеспечивает различие между conj и adjoint поскольку они согласятся по скалярам, ​​но по-разному ведут себя с массивами.

В этом случае должен conj(A) вызывать conj для каждого элемента или вызывать adjoint ? Пример представления комплексных чисел в виде матриц 2x2 предполагает, что conj(A) должен фактически вызывать adjoint для каждого элемента, а не вызывать conj . Это сделает adjoint , conj и conj. все разные операции:

  1. adjoint : поменять местами индексы i и j и рекурсивно сопоставить adjoint по элементам.
  2. conj : сопоставить adjoint над элементами.
  3. conj. : сопоставить conj над элементами.

conj(A) должен вызывать conj для каждого элемента. Если вы представляете комплексные числа матрицами 2x2, у вас другое комплексное векторное пространство.

Например, обычное использование сопряжения векторов - это анализ собственных значений вещественных матриц : собственные значения и собственные векторы входят в комплексно-сопряженные пары. Теперь предположим, что у вас есть блочная матрица, представленная 2d-массивом A реальных матриц 2x2, действующим на заблокированные векторы v представленные 1d-массивами 2-компонентных векторов. Для любого собственного значения λ из A с собственным вектором v мы ожидаем второе собственное значение conj(λ) с собственным вектором conj(v) . Это не сработает, если conj adjoint рекурсивно вызывает

(Обратите внимание, что сопряженный оператор, даже для матрицы, может отличаться от сопряженно-транспонированного, потому что сопряженный линейный оператор, определенный в самом общем виде, зависит также от выбора внутреннего продукта. Существует множество реальных приложений, в которых некоторые тип взвешенного внутреннего продукта является подходящим, и в этом случае соответствующий способ взятия сопряженного элемента матрицы изменяется! Но я согласен с тем, что, учитывая Matrix , мы должны взять сопряженное, соответствующее стандартному внутреннему продукту, заданному формулой dot(::Vector,::Vector) . Однако вполне возможно, что некоторые типы AbstractMatrix (или другие линейные операторы) захотят переопределить adjoint чтобы сделать что-то другое.)

Соответственно, существует также некоторая трудность в определении алгебраически разумного adjoint(A) например, для трехмерных массивов, поскольку мы не определили трехмерные массивы как линейные операторы (например, нет встроенной операции array3d * array2d ) . Может быть, adjoint следует определять по умолчанию только для скаляров, 1d и 2d массивов?

Обновление: О, хорошо: мы не определяем ctranspose сейчас и для трехмерных массивов. Продолжать.

(ОТ: Я уже с нетерпением жду «Серьезно относиться к 7-тензорам», следующей части очень успешного мини-сериала из 6 частей ...)

Если вы представляете комплексные числа матрицами 2x2, у вас другое комплексное векторное пространство.

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

m(z::Complex) = [z.re -z.im; z.im z.re]

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

conj(m.(v)) == m.(conj(v))

Я бы подробно изложил этот пример и сравнил бы с ' который должен уже ездить с m но я не могу из-за https://github.com/JuliaLang/julia/ issues / 20979 , что случайно нарушает эту коммутацию. Как только эта ошибка будет исправлена, m.(v)' == m.(v') останется в силе , но если я правильно вас понимаю, conj(m.(v)) == m.(conj(v)) не должны?

conj(m(z)) следует == m(z) .

Ваш m(z) является изоморфизмом комплексных чисел при сложении и умножении, но это не тот же объект в других отношениях для линейной алгебры, и мы не должны притворяться, что это для conj . Например, если z - комплексный скаляр, тогда eigvals(z) == [z] , но eigvals(m(z)) == [z, conj(z)] . Когда трюк m(z) распространяется на комплексные матрицы, это удвоение спектра имеет сложные последствия, например, для итерационных методов (см., Например, эту статью ).

Другими словами, z уже является комплексным векторным пространством (векторным пространством над комплексными числами), но вещественная матрица 2x2 m(z) является векторным пространством над действительными числами (вы может умножить m(z) на действительное число) ... если вы "усложните" m(z) , умножив его на комплексное число, например, m(z) * (2+3im) ( не m(z) * m(2+3im) ), тогда вы получить другое комплексное векторное пространство, больше не изоморфное исходному комплексному векторному пространству z .

Похоже, это предложение приведет к реальному улучшению: +1: Я думаю о математической стороне:

  • как я понимаю, сопряжение - это действие на кольцо (читай: числа), которое обеспечивает коэффициенты для нашего векторного пространства / векторов. Это действие индуцирует другое действие на векторном пространстве (и на его отображениях, читай: на матрицы), также называемое сопряжением, из-за линейности построения векторного пространства над кольцом. Следовательно, сопряжение обязательно работает путем поэлементного применения к коэффициентам. Для Джулии это означает, что по сути для вектора v , conj(v) = conj.(v) , и это выбор дизайна, имеет ли conj методы также для массивов или только для скаляров, оба из которых кажется нормально? (Стивен считает, что пример комплексных чисел как 2x2-матриц состоит в том, что это векторное пространство, коэффициенты которого формально / фактически действительны, поэтому конъюгация здесь не действует.)
  • adjoint имеет алгебраическое значение, которое является фундаментальным во многих важных областях, тесно связанных с линейной алгеброй (см. 1, 2 и 3 , все они также имеют большое применение в физике). Если добавляется adjoint , это должно разрешать аргументы ключевого слова для рассматриваемого действия - в векторных пространствах / функциональном анализе это действие обычно вызывается внутренним продуктом, следовательно, аргументом ключевого слова может быть форма. Что бы ни было разработано для транспонирования Джулии, следует избегать конфликтов с этими приложениями, поэтому я думаю, что adjoint немного дорого обходится?

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

Мое предпочтение было бы немного проще:

  • Оставить conj как есть (поэлементно).
  • Сделайте a' соответствующим adjoint(a) и сделайте его всегда рекурсивным (и, следовательно, неуспешным для массивов строк и т.д., где adjoint не определено для элементов).
  • Сделайте a.' соответствующим transpose(a) и сделайте его никогда не рекурсивным (просто permutedims ) и, следовательно, игнорируйте тип элементов.

Я действительно не вижу варианта использования нерекурсивного adjoint . В качестве растяжки я могу представить себе варианты использования рекурсивного transpose , но в любом приложении, где вам нужно изменить a.' на transpose(a, recur=true) это кажется таким же простым вызвать другую функцию transposerecursive(a) . Мы могли бы определить transposerecursive в Base, но я думаю, что потребность в этом будет настолько редкой, что нам следует подождать, чтобы увидеть, действительно ли это появится.

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

До сих пор (большинство) наших процедур линейной алгебры довольно сильно укоренились в стандартных структурах массивов и стандартном внутреннем продукте. В каждый слот вашей матрицы или вектора вы помещаете элемент своего «поля». Я утверждаю, что для элементов полей нас интересуют + , * и т. Д. И conj , но не transpose . Если ваше поле было особым сложным представлением, которое немного похоже на вещественную матрицу 2x2, но изменяется под conj , тогда ничего страшного - это свойство conj . Если это просто реальный AbstractMatrix размером 2x2, который не меняется при conj , то, возможно, такие элементы матрицы не должны изменяться при сопряжении ( @stevengj сказал это лучше, чем я могу описать - может существовать изоморфизм комплексных чисел, но это не значит, что он ведет себя одинаково во всех отношениях).

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

Итак, мое "упрощенное" предложение было бы таким:

  • Оставьте conj как есть (или сделайте просмотр за AbstractArray s)
  • Сделайте a' нерекурсивным представлением так, чтобы (a')[i,j] == conj(a[j,i])
  • Сделайте a.' нерекурсивным представлением так, чтобы (a.')[i,j] == a[j,i]
  • Ввести тип BlockArray для обработки блочных матриц и т. Д. (В Base или, возможно, в хорошо поддерживаемом пакете). Возможно, для этой цели это было бы гораздо более мощным и гибким, чем Matrix{Matrix} , но столь же эффективным.

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

PS - @StefanKarpinski По практическим соображениям логический аргумент ключевого слова для рекурсии не работает с представлениями для транспонирования. Тип представления может зависеть от логического значения.

Кроме того, я упоминал в другом месте, но добавлю это здесь для полноты: рекурсивные транспонированные представления имеют раздражающее свойство, заключающееся в том, что тип элемента может измениться по сравнению с массивом, который он обертывает. Например, transpose(Vector{Vector}) -> RowVector{RowVector} . Я не придумал, как получить этот тип элемента для RowVector без штрафа во время выполнения или путем вызова вывода для вычисления типа вывода. Я предполагаю, что текущее поведение (выполнение логического вывода) нежелательно с точки зрения языка.

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

@stevengj - вы указываете на то, что "сложные" матрицы 2x2, являющиеся формально реальным векторным пространством, а не сложным векторным пространством, имеет смысл для меня, но тогда этот момент ставит под сомнение исходную мотивацию для рекурсивного сопряжения, что заставляет меня задаться вопросом, а не Предложение @andyferris не было бы лучше (нерекурсивное транспонирование и присоединение). Я предполагаю, что тот факт, что и комплексный пример 2x2, и представление блочной матрицы «хотят», чтобы сопряженное соединение было рекурсивным, наводит на размышления, но, учитывая ваши комментарии к этому первому примеру, я должен задаться вопросом, нет ли других случаев, когда нерекурсивное сопряжение правильнее / удобнее.

Если сопряженное не рекурсивное, оно не является сопряженным. Это просто неправильно.

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

Сопряженный вектор должен быть линейным оператором, отображающим его на скаляр. То есть a'*a должен быть скаляром, если a - вектор. И это индуцирует соответствующий сопряженный элемент для матриц, поскольку определяющим свойством является a'*A*a == (A'*a)'*a .

Если a - вектор векторов, это означает, что a' == adjoint(a) должен быть рекурсивным.

Хорошо, я думаю, что слежу за этим.

У нас также есть рекурсивные внутренние продукты:

julia> norm([[3,4]])
5.0

julia> dot([[3,4]], [[3,4]])
25

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

Я думаю, что главный вопрос в таком случае: требуется ли нам a' * b == dot(a,b) для всех векторов a , b ?

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

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

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

Понимаю. Итак, переименование ' в adjoint будет частью изменения, чтобы было очевидно, что это не conj ∘ transpose ?

В рекурсивных массивах меня всегда смущала одна вещь: что такое скаляр в этом контексте? Во всех случаях, с которыми я знаком, говорится, что элементы вектора BlockArray ). Вы ожидаете, что скаляры смогут умножаться при * , а тип скаляра обычно не отличается для вектора и его двойственности.

@andyferris , для общего векторного пространства скаляры представляют собой кольцо (числа), на которое можно умножать векторы. Я могу сделать 3 * [[1,2], [3,4]] , но не могу [3,3] * [[1,2], [3,4]] . Итак, даже для Array{Array{Number}} правильный скалярный тип - Number .

Согласен, но элементы обычно считаются (одними и теми же) скалярами, не так ли?

В любом случае, методы, которые я видел, начинаются с кольца и построения из него векторного пространства. Кольцо поддерживает + , * , но я не видел лечения, которое требовало бы поддержки adjoint , dot или чего-то еще.

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

Это зависит от того, что вы подразумеваете под «стенографией» и что вы подразумеваете под «элементами».

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

image

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

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

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

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

Я просто пытаюсь понять получше, не делая особого акцента :)

Конечно, в вышеизложенном присоединение рекурсивно. Мне было интересно, например, в приведенном выше примере лучше рассматривать [E, H] как BlockVector чьи (бесконечно?) Многие элементы являются сложными, или как 2-вектор, элементы которого являются векторными полями. Я думаю, что в этом случае подход BlockVector был бы неработоспособным.

В любом случае, спасибо.

Так что, возможно, я смогу изложить свои мысли в такой форме:

Для вектора v я как бы ожидаю, что length(v) описывает размерность векторного пространства, то есть количество скалярных коэффициентов, которые мне нужны для (полностью) описания элемента векторного пространства, и также v[i] возвращает скалярный коэффициент i th. На сегодняшний день я не думал о AbstractVector как об «абстрактном векторе», а как об объекте, который содержит коэффициенты элемента некоторого векторного пространства, заданного некоторой базой, о которой знает программист.

Это казалось простой и полезной ментальной моделью, но, возможно, это слишком ограничительно / непрактично.

(РЕДАКТИРОВАТЬ: это ограничение, потому что в Vector{T} T должны вести себя как скаляр для операций линейной алгебры.)

но я не могу сделать [3,3] * [[1,2], [3,4]]

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

Я не уверен, что это желательно ... в этом случае [3,3] самом деле не является скаляром. (У нас также уже есть возможность делать [3,3]' * [[1,2], [3,4]] - что я действительно не знаю, как интерпретировать в смысле линейной алгебры).

Это показывает интересный случай: мы, кажется, говорим, что v1' * v2 - это внутренний продукт (и, таким образом, возвращает «скаляр»), но [3,3]' * [[1,2], [3,4]] == [12, 18] . Думаю, это надуманный пример.

В этом случае [3,3] - это скаляр: скаляры - это элементы в нижележащем кольце, и существует множество хороших способов превратить элементы формы [a,b] в кольцо (и, таким образом, определить векторы / матрицы над ним). Тот факт, что они записаны как векторы, или тот факт, что это кольцо само образует векторное пространство над другим нижележащим кольцом, этого не меняет. (Вы можете использовать любую другую нотацию, которая может скрыть векторы! Или сделать ее совершенно другой.) Как только понял @andyferris , то, что такое скаляр, зависит от контекста.

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

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

В этом случае [3,3] является скаляром: скаляры - это элементы в нижележащем кольце

За исключением того, что такой скаляр не является элементом кольца, определяемого + , * . Он не поддерживает * ; Я не умею делать [3,3] * [3,3] .

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

Я согласен, если мы хотим провести практическую работу, это совершенно нормально, и это то, что мы делали до сих пор, но мне кажется, что лежащие в основе скаляры являются скалярами полностью уплощенного массива, и это то, что определяется линейной алгеброй. . У нас есть технология для создания BlockVector и BlockMatrixBlockDiagonal и т. Д.), Которые представляют эффективный, плоский вид полностью развернутого массива, так что "подход должен быть как минимум выполнимым. Я также , случается, думаю , что это будет так же , как пользователь дружественный как рекурсивный подход (несколько дополнительных символов , чтобы набрать BlockMatrix([A B; C D]) в обмен на то , что может быть лучше , семантический и потенциально более читаемый код - Я уверен , что эти вопросы обсуждаются). Однако для реализации всего этого потребовалось бы больше работы.

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

(О скалярах: скаляры не обязательно являются элементами полностью уплощенной структуры! Причина в следующем. 1-мерное комплексное векторное пространство является одномерным по отношению к комплексным числам и двумерным по отношению к действительным числам; ни одно из представлений не является специальные. Кватернионы двумерны над комплексными числами. Октонионы двумерны над четвертионами, четырехмерны над комплексными числами и 8-мерны над реалами. Но нет никаких оснований утверждать, что октонионы больше "8-мерны" чем настаивать на том, что их скаляры являются действительными числами, а не комплексными; это выбор, не обусловленный логикой. Это чисто вопрос представления, пользователю должна быть предоставлена ​​эта свобода, поскольку линейная алгебра одинакова в каждом случае. И вы получаете гораздо более длинные цепочки таких пространств с [усеченными] кольцами мультиполиномов или алгебраическими расширениями числовых полей, оба из которых имеют живое применение с матрицами. Джулии не нужно заходить так далеко в кроличью нору - но мы следует помнить, что он существует, чтобы не блокировать его. Может тема дискурса? :))

@felixrehren , вектор векторов над кольцом R лучше всего понимать как пространство прямой суммы, которое также является векторным пространством над R. Бессмысленно называть сами векторы «нижележащим кольцом», потому что в целом они не являются кольцо. Это рассуждение прекрасно применимо к [[1,2], [3,4]] .

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

Рассмотрим случай «двухкомпонентных векторов-столбцов» (| u⟩, | v⟩) двух элементов | u⟩ и | v⟩ в некотором гильбертовом пространстве H над некоторым кольцом (скажем, комплексные числа ℂ) в обозначениях Дирака: "векторы векторов". Это гильбертово пространство прямой суммы H⊕H с естественным скалярным произведением ⟨(| u⟩, | v⟩), (| w⟩, | z⟩)⟩ = ⟨u | w⟩ + ⟨v | z⟩. Поэтому сопряженный должен производить линейный оператор , состоящие из «вектора - строки» (⟨u | ⟨v |), элементы которого сами по себе являются линейными операторами: присоединенный является«рекурсивный» . Это полностью отличается от комплексного сопряжения, которое дает элемент того же гильбертова пространства H⊕H, индуцированный сопряжением подчиненного кольца.

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

Другими словами, тип объекта должен указывать на две разные части информации:

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

Для Vector{T<:Number} мы должны читать это как указание на то, что скалярное кольцо - это T (или complex(T) для комплексного векторного пространства) и что внутреннее произведение является обычным евклидовым .

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

Если у вас есть скаляр T<:Number , тогда conj(x::T) == adjoint(x::T) .

Итак, если вы попытаетесь представить комплексные числа z::Complex{T} матрицами 2x2 m(z)::Array{T,2} , тогда ваш тип будет указывать как на другое кольцо T и на другой внутренний продукт по сравнению с z , поэтому не следует ожидать, что conj или adjoint дадут эквивалентные результаты.

Я вроде как понимаю, о чем вы говорите @felixrehren. Если скаляр действительный, то октерниан можно рассматривать как 8-мерное векторное пространство. Но если скалярный коэффициент был октернианом, то существует тривиальный базис размерности 1, который представляет все октернианы. Я не уверен, отличается ли это от того, что я сказал (или того, что я имел в виду: smile :), если у нас есть AbstractVector{T1} он может быть изоморфен AbstractVector{T2} другой размерности ( length ). Но T1 и T2 должны быть кольцами под операторами Джулии + и * (и изоморфизм может сохранить поведение + но не * , или внутренний продукт или сопряженный).

@stevengj Я всегда думал о прямой сумме точно так BlockVector . У вас есть две (непересекающиеся) неполные базы. Внутри каждого базиса вы можете образовывать суперпозиции, такие как c₁ | X₁⟩ + c₂ | X₂⟩ в первом базисе или c₃ | Y₁⟩ + c₄ | Y₂⟩ во втором. «Прямая сумма» представляет собой пространство состояний, которые выглядят как (c₁ | X₁⟩ + c₂ | X₂⟩) + (c₃ | Y₁⟩ + c₄ | Y₂⟩). Мне кажется, что единственная особенность, которая отделяет это от базиса размерности четыре над комплексными числами (т.е. такими состояниями, как c₁ | X₁⟩ + c₂ | X₂⟩ + c₃ | Y₁⟩ + c₄ | Y₂⟩), - это скобки - мне это кажется условным; прямая сумма - это просто удобный способ записать их на бумаге или с массивами на компьютере (и может быть полезно, например, чтобы позволить нам каталогизировать и использовать симметрии, или разделить проблему (возможно, чтобы распараллелить ее) и т. д. ). В этом примере X⊕Y по-прежнему является четырехмерным векторным пространством, в котором скаляры являются комплексными.

Это то, что заставляет меня хотеть eltype(v) == Complex{...} и length(v) == 4 а не eltype(v) == Vector{Complex{...}} и length(v) == 2 . Это помогает мне видеть и рассуждать о лежащем в основе кольце и векторном пространстве. Разве это не «сплющенное» пространство, где вы можете выполнять «глобальное» транспонирование и поэлементно conj для вычисления сопряженного?

(у вас было два сообщения, а я писал только один, @stevengj : smile :)

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

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

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

Опять же, я не уверен, о чем вы говорите. Вы серьезно предлагаете, чтобы eltype(::Vector{Vector{Complex}}) в Julia было Complex , или что length должно возвращать сумму длин? Что каждый в Джулии должен быть вынужден принять ваш «уплощенный» изоморфизм для пространств с прямой суммой? Если нет, то сопряженное должно быть рекурсивным.

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

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

Я определенно не предлагал этого вообще.

Опять же, я не уверен, о чем вы говорите

Я серьезно предлагаю, чтобы если вы хотите заниматься линейной алгеброй с AbstractArray , тогда eltype лучше будет кольцом (поддержка + , * и conj ), что исключает, например, eltypes Vector потому что [1,2] * [3,4] не работает. А length вектора на самом деле будет представлять размерность вашей линейной алгебры. Да, это исключает бесконечно-размерные векторные пространства, но обычно в Julia AbstractVector не может быть бесконечного размера. Я считаю, что это упростит понимание того, как реализовать и использовать функциональность линейной алгебры в Julia. Однако пользователям придется явно обозначать прямую сумму через BlockArray или аналогичный, а не использовать структуры вложенных массивов.

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

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

Вы серьезно предлагаете, чтобы eltype(::Vector{Vector{Complex}}) в Julia было Complex , или что length должно возвращать сумму длин?

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

Вы хотите сказать, что все в Julia должны быть вынуждены принять ваш «плоский» изоморфизм для пространств с прямой суммой?

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

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

  • Оставить conj как есть (поэлементно).
  • Сделайте a' соответствующим adjoint(a) и сделайте его всегда рекурсивным (и, следовательно, неуспешным для массивов строк и т.д., где сопряженные элементы не определены).
  • Сделайте a.' соответствующим transpose(a) и сделайте его никогда не рекурсивным (просто permutedims ), и, следовательно, игнорируйте тип элементов.

Основные фактические "задачи", которые можно извлечь из этого:

  1. [] Переименуйте ctranspose в adjoint .
  2. [] Измените transpose на нерекурсивный.
  3. [] Выясните, как это согласуется с векторами-строками.

Я подозреваю, что использование рекурсивного транспонирования достаточно редко, и мы можем просто изменить это в 1.0 и указать в НОВОСТИ как нарушение. Изменение ctranspose на adjoint может пройти обычное устаревание (однако мы делаем это для версии 1.0). Что еще нужно сделать?

@StefanKarpinski , нам также необходимо внести соответствующие изменения в RowVector . Одна из возможностей - разделить RowVector на типы Transpose и Adjoint для ленивых нерекурсивных transpose и ленивых рекурсивных adjoint соответственно, и избавиться от Conj (ленивый conj ).

Еще несколько, хотя бы косвенно связанных изменений

  • Некоторые типы представлений для transpose и adjoint из AbstractMatrix
  • Сделайте adjoint(::Diagonal) рекурсивным (возможно, мы должны исправить эту "ошибку" в Diagonal для transpose и ctranspose до Julia v0.6.0)
  • Используйте правильный "скалярный" тип в таких вещах, как: v = Vector{Vector{Float64}}(); dot(v,v) (в настоящее время ошибка - пытается вернуть zero(Vector{Float64}) )

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

В чем я застрял и что напрямую мотивировало все мои пункты обсуждения выше, так это то, как выполнять операции линейной алгебры с такой структурой, включая inv , det , expm , eig и так далее. Например, есть метод для det(::Diagonal{T}) where T который просто берет произведение всех диагональных элементов. Прекрасно работает для T <: Number , а также, если все элементы являются квадратными матрицами одинакового размера . (Конечно, если это квадратные матрицы одинакового размера, тогда элементы образуют кольцо, и все это вполне разумно - также рекурсивное транспонирование (edit: adjoint) - правильная вещь).

Однако, если мы думаем об общей блочно-диагональной матрице Diagonal{Matrix{T}} или любой блочно-матрице Matrix{Matrix{T}} , то в общем случае мы можем говорить о ее детерминанте как о скаляре T . То есть, если бы размер был (3 ⊕ 4) × (3 ⊕ 4) (матрица 2 × 2 с диагональными элементами, которые являются матрицами размером 3 × 3, 4 × 4 и совпадающими недиагональными элементами), det вернуть детерминант «уплощенной» структуры 7 × 7, или он должен просто попытаться умножить элементы 2 × 2, как они есть (и с ошибкой, в данном случае)?

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

У меня нет проблем с рекурсивностью a' , но я лично считаю новую нотацию a.' крайне запутанной. Если бы вы показали мне синтаксис a.' я бы сказал вам, основываясь на моей ментальной модели Джулии, что он выполняет transpose.(a) т.е. поэлементное транспонирование a , и я был бы полностью неправильно. В качестве альтернативы, если бы вы показали мне, что a' и a.' являются вариантами транспонирования, и что один из них также рекурсивен поэлементно, я бы сказал вам, что a.' должен иметь поэлементная рекурсия, и я снова ошибаюсь.

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

@rdeits Я боюсь, что этот синтаксис происходит от MATLAB. Это стало немного прискорбно, когда в v0.5 был введен (довольно замечательный) синтаксис широковещательной рассылки dot-call.

Мы могли бы создать свой собственный путь отдельно от MATLAB и изменить это - но я считаю, что где-то было отдельное обсуждение этого (кто-нибудь помнит, где?).

Ах, это прискорбно. Благодаря!

Еще одно задание:

  • [] Исправьте issymmetric и ishermitian чтобы они совпадали с transpose и adjoint - первое из каждой пары было нерекурсивным, второе из каждой пары было рекурсивным.

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

Давайте перенесем обсуждение синтаксиса транспонирования сюда: https://github.com/JuliaLang/julia/issues/21037.

У меня нет твердого мнения о рекурсивности transpose / ctranspose / adjoint , но я бы не стал рассматривать A::Matrix{Matrix{T}} как блочную матрицу в чувство ленивого hvcat , которое @andyferris, похоже, подразумевает хотя бы частично. То есть, если бы все элементы A были квадратными матрицами одинакового размера (т. Е. Образовывали кольцо), я бы ожидал, что det(A) снова вернет квадрат такого размера. Если они прямоугольные или разных размеров, я бы ожидал ошибки.

Тем не менее, блочная матрица / тип ленивого кота может быть полезен, но он должен идти до конца и также определять, например, getindex для работы с сглаженными данными. Но я определенно не хотел бы, чтобы эта концепция была поглощена Matrix или любым другим из существующих типов матриц, например Diagonal .

Это успокаивает, @martinholters. То, о чем я паниковал ранее в этой беседе, было идеей, что мы каким-то образом должны иметь возможность применить всю структуру линейной алгебры Джулии к Matrix{Matrix} для произвольных блочных матриц (где подматрицы имеют разные размеры).

То, что я доказывал с помощью «уплощения», не заключается в том, чтобы ctranspose / adjoint расширяет это, позволяя элементам действовать как линейные операторы, что кажется правильным и полезным. (Другой случай - это элементы вектора, которые действуют как вектор, где рекурсивное сопряжение также является правильным).

Вернемся немного назад - какова была первоначальная мотивация для удаления поведения отказа по умолчанию для transpose(x) = x и ctranpsose(x) = conj(x) ? Они всегда казались мне весьма полезными.

Вернемся немного назад - какова была первоначальная мотивация для удаления поведения по умолчанию при отсутствии операций для transpose (x) = x и ctranpsose (x) = con (x)? Они всегда казались мне весьма полезными.

Это было мотивировано пользовательскими типами линейных операторов (которые не могут быть подтипами AbstractArray), которые не смогли специализировать ctranspose . Это означало, что они унаследовали неправильное бездействие от запасного варианта. Мы попытались структурировать отправку таким образом, чтобы резервные копии никогда не были неверными (но они могут иметь пессимистическую сложность). https://github.com/JuliaLang/julia/issues/13171

Кажется, у нас есть два варианта - в обоих transpose становится нерекурсивным, иначе:

  1. Нерекурсивный ctranspose .
  2. Рекурсивный ctranspose .

@stevengj , @jiahao , @andreasnoack - какие у вас здесь предпочтения? Другие?

Я много думал об этом с момента выступления @jiahao на JuliaCon 2017.

На мой взгляд, линейная алгебра должна определяться по отношению к «скалярному» полю как элемент типа T . Если T - это поле (поддерживает + , * и conj (также - , / , .. .)), то я не понимаю, почему методы в Base.LinAlg должны терпеть неудачу.

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

Для меня ключевым моментом здесь является то, что для того, чтобы заставить работать массив «произвольного порядка», вы должны научить свои «скаляры» некоторым операциям, которые обычно определяются только для векторов и матриц. Текущий метод Джулии, который делает это, - transpose(x::Number) = x . Однако я бы предпочел, чтобы мы расширили семантическое различие между массивами и скалярами, удалив этот метод - и я думаю, что это может быть вполне осуществимо.

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

  • Имейте какую-то особенность "массивности", чтобы transpose(::AbstractMatrix{T}) рекурсивным, когда T - это AbstractMatOrVec , а не когда T - это Number , и сделать его каким-либо образом настраиваемым (согласие, отказ, что угодно) в противном случае. (В некотором роде это было и в настоящее время делается путем управления поведением метода transpose для элементов, но я думаю, что лучше контролировать поведение метода transpose для (внешнего ) массив).
  • В качестве альтернативы можно утверждать, что большинство AbstractArray включая Array s, относятся к первому порядку (их элементы являются скалярными полями), и использовать другой тип AbstractArray (скажем, NestedArray ), чтобы обернуть массивы и «отметить», что их элементы должны обрабатываться как массивы, а не скаляры. Я попытался немного поиграть с этим, но мне показалось, что вышеупомянутый вариант, вероятно, меньше обременяет пользователей.

Я чувствую, что Джулия проводит сильное семантическое разделение между массивами и скалярами, и что текущая ситуация кажется (мне) немного несовместимой с этим, поскольку скаляры нужно было «обучать» свойству массива transpose . Пользователь может четко сказать, что Matrix{Float64} - это матрица скаляров (массив первого порядка), а Matrix{Matrix{Float64}} - это блочная матрица (массив второго порядка). Для нас может быть более последовательным использование системы типов или свойств, чтобы определить, является ли массив «первым порядком» или чем-то еще. Я также думаю, что первый вариант, который я перечислил, сделает Джулию более удобной для пользователя (так что я могу сделать ["abc", "def"].' ), но сохранит гибкость, чтобы делать некоторые разумные / полезные вещи по умолчанию с блочными матрицами.

Извини, что так долго продолжаю , но после разговора с @StefanKarpinski, "работать", но для меня это будет "работать" так же, как матричная / векторная алгебра "работала" до введения RowVector .

TL; DR был - не делайте ( c ) transpose рекурсивным или нет - пусть он делает и то, и другое (то есть настраивается).

Это хорошие моменты, но просто хочу отметить, что почти все (если не все) жалобы на рекурсивность (c)transpose связаны с нематическим использованием ' и .' при преобразовании, например, Vector{String} и Vector{PyObject} в матрицы размером 1xn. Тип за типом легко исправить, но я вижу, что это раздражает. Однако считалось, что рекурсивное определение было математически правильным и что «правильное, но во многих случаях раздражающее» лучше, чем «удобно, но в редких случаях неверно».

Возможным решением может быть ваше предложение в первом пункте, то есть только рекурсивное транспонирование для элементов, подобных массиву. Я считаю, что T<:AbstractVecOrMat подойдет в большинстве случаев, когда статус меняется на «удобно, но в очень редких случаях - неправильно». Причина, по которой он все еще может быть ошибочным, заключается в том, что некоторые операторные типы не подходят для категории AbstractMatrix потому что интерфейс AbstractArray в основном связан с семантикой массива ( getindex ) , а не линейная алгебра.

@andyferris , сопряженное и двойственное к скаляру прекрасно определены, и ctranspose(x::Number) = conj(x) является правильным.

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

Лично я думаю, что попытка взглянуть на массивы блоков как на вложенные Arrays становится сложной по причинам, указанным здесь, и, возможно, лучше иметь выделенный тип à la https://github.com/KristofferC/BlockArrays.jl для это.

Красиво, @KristofferC.

Есть один очень важный момент, который AbstractArray s. Нам определенно нужен способ рекурсивного (c) транспонирования для них - не уверен, думали ли вы об этом или нет, но я подумал, что упомяну об этом.

Основные моменты вялого / # linalg разговора на эту тему. Повторяет некоторые из вышеупомянутых веток. Сосредоточен на семантике, избегает орфографии. (Спасибо всем, кто участвовал в этом разговоре! :))

Существуют три семантически различных операции:
1) «математически сопряженный» (рекурсивный и ленивый)
2) «математическое транспонирование» (в идеале рекурсивное и ленивое)
3) «структурное транспонирование» (в идеале нерекурсивное и «нетерпеливое»?)

Текущая ситуация: «математическое сопряжение» отображается в ctranspose , «математическое транспонирование» - в transpose , а «структурное транспонирование» - в permutedims(C, (2, 1)) для двумерного C и reshape(C, 1, length(C)) для одномерного C . Проблема: «структурное транспонирование» - обычная операция, и история с permutedims / reshape на практике несколько сбивает с толку / неестественна / раздражает.

Как это возникло: ранее «структурное транспонирование» объединялось с «математическим сопряженным» / «математическим транспонированием» с помощью общих резервных вариантов, таких как transpose(x::Any) = x , ctranspose(x::Any) = conj(x) и conj(x::Any) = x . Эти резервные варианты сделали [c]transpose и связанные с ними постфиксные операторы ' / .' в большинстве случаев служат для «структурного транспонирования». Отлично. Но они также сделали операции, связанные с [c]transpose на некоторых определяемых пользователем числовых типах, без предупреждения (возвращают неверные результаты) при отсутствии определения специализаций [c]transpose для этих типов. Ой. Следовательно, эти общие резервные варианты без операций были удалены, что привело к нынешней ситуации.

Вопрос в том, что теперь делать.

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

Существуют два широких предложения:

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

(2) Рожок для обуви три операции на две. Существуют три формы этого предложения:

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

(2b) Введите черту, указывающую на «математичность» типа. При применении присоединения / транспонирования к объекту, если типы контейнера / элемента являются "математическими", рекурсивно; если нет, не рекурсивно. Плюсы: две операции. Может охватывать множество распространенных случаев. Недостатки: страдает той же проблемой, что и обычные резервные варианты [c]transpose операций, т.е. может незаметно давать неверные результаты для определяемых пользователем числовых типов без необходимых определений признаков. Не позволяет применять структурное транспонирование к «математическому» типу или математическое транспонирование к «нематическому» типу. Непонятно, как определить, является ли объект «математикой» во всех случаях. (Например, что, если тип элемента является абстрактным? Рекурсивное / нерекурсивное решение - это время выполнения и поэлементно, или время выполнения и требует первой проверки всех элементов в контейнере для проверки их коллективного типа?)

(2c) Сохраните adjoint ( ctranspose ) как «математическое сопряжение» и transpose «математическое транспонирование» и введите специализированные (не общие резервные) методы для adjoint / transpose для нечисловых скалярных типов (например, adjoint(s::AbstractString) = s ). Плюсы: две операции. Правильная математическая семантика. Недостатки: требуется частичное определение adjoint / transpose для нечисловых типов, что затрудняет общее программирование. Не допускает применения структурного транспонирования к «математическим» типам.

Предложения (1) и (2a) получили значительно более широкую поддержку, чем (2b) и (2c).

Дополнительную информацию можно найти в # 19344, # 21037, # 13171 и slack / # linalg. Лучший!

Спасибо за красивую статью!

Я хотел бы добавить в таблицу еще одну возможность, которая подошла бы к варианту 1: иметь разные типы контейнеров для математических матриц и табличных данных. Тогда значение A' можно определить по типу A (примечание: не по типу элемента, как обсуждалось выше). Минусы здесь в том, что для этого может потребоваться много convert s между двумя (не так ли?) И, конечно, будет очень разрушительно. По общему признанию, я более чем скептически отношусь к тому, оправдают ли выгоды это нарушение, но все же хотел упомянуть об этом.

Спасибо, отличное описание. Голосую за (1).

Отличное написание. Обратите внимание, что для (1) варианты написания могут быть такими:

  • Сопутствующий: a' (рекурсивный, ленивый)
  • Математическое транспонирование: conj(a') (рекурсивный, ленивый)
  • Структурное транспонирование: a.' (нерекурсивное, нетерпеливое)

Таким образом, не обязательно вводить какой-либо новый оператор - математическое транспонирование будет просто (ленивой и, следовательно, эффективной) композицией сопряжения и сопряжения. Самая большая проблема заключается в том, что он делает два похожих оператора, то есть postfix ' и .' , совершенно разными семантически. Тем не менее, я бы сказал, что в наиболее правильном универсальном коде, использовал ли кто-то ' или .' это на 99% точный индикатор того, имели ли они в виду «дайте мне сопутствующий элемент этой вещи» или «обменять размеры этой вещи ». Более того, в случаях, когда кто-то использовал ' и на самом деле имел в виду «поменять местами размеры этой вещи», их код уже был бы некорректным для любой матрицы с элементами, скалярное сопряжение которых нетривиально, например комплексные числа. В нескольких оставшихся случаях, когда кто-то на самом деле имел в виду «дайте мне конъюгат сопряженного с этим», я бы сказал, что запись conj(a') делает это значение более ясным, поскольку на практике люди фактически используют a.' для означают "поменять местами размеры a ".

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

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

Например, vector.' больше не обязательно должно быть RowVector (поскольку RowVector - концепция линейной алгебры, наша первая попытка создать "двойственный" вектор) - это может просто быть Matrix . Когда мне нужен «несопрягающий транспонирование» в линейной алгебре, на самом деле я беру conj(adjoint(a)) , и это то, что мы будем использовать и рекомендовать всем пользователям линейной алгебры (до сих пор у меня имел давнюю «плохую» привычку начиная с MATLAB просто использовать a.' вместо a' транспонирования любой матрицы (или вектора) , который я знал , был реальным, когда то , что я действительно хотел был adjoint (или двойное) - смена названия здесь очень поможет).

Я также кратко отмечу, что это оставляет открытое интересное пространство. Ранее vector' и vector.' должны были удовлетворить двойные потребности выполнения «транспонирования данных» и взятия чего-то вроде двойного вектора. Теперь, когда .' предназначен для управления размерами массивов, а ' представляет собой концепцию линейной алгебры, мы могли бы изменить RowVector на 1D DualVector или что-то еще целиком. (пожалуй, не стоит здесь это обсуждать - если у кого-то есть аппетит, давайте сделаем отдельный вопрос.)

Напоследок скопирую предлагаемый план действий из Slack:

1) переместите transpose из LinAlg в Base и добавьте предупреждения (только 0.7) о том, что он больше не создает RowVector или является рекурсивным (если это возможно)
2) переименуйте ctranspose в adjoint везде
3) убедитесь, что Vector , ConjVector и RowVector работают с комбинациями ' и conj и * , возможно переименовать RowVector . (здесь мы также делаем conj(vector) ленивым).
4) ввести ленивую матрицу, сопряженную, которая также хорошо взаимодействует с conj и ConjMatrix
5) удалите A_mul_Bc и т. Д. Переименуйте A_mul_B! в mul! (или *! ).
6) profit (прибыль)

@stevengj написал:

@andyferris , сопряженное и двойственное к скаляру прекрасно определены, и ctranspose(x::Number) = conj(x) является правильным.

Для протокола, я определенно согласен с этим.

Мне кажется, что большинство применений транспонирования - это "нематия", а большинство применений ctranspose (...) - математические.

Итак, идея состоит в том, чтобы формализовать это: все использования transpose становятся "нематическими", а единственное использование adjoint будет "математическим".

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

Однако мы по-прежнему хотим, чтобы .' был ленивым. например, X .= f.(x, y.') прежнему не выделяет.

Вот вопрос: как реализовать issymmetric(::AbstractMatrix{<:AbstractMatrix}) ? Будет ли это нерекурсивная проверка на соответствие transpose ? Я смотрю на реализацию; предполагается, что элементы могут transpose . OTOH кажется вполне правильным проверить, если issymmetric(::Matrix{String}) ...

В настоящее время он не является рекурсивным, если заключен в Symmetric btw

julia> A = [rand(2, 2) for i in 1:2, j in 1:2]; A[1, 2] = A[2, 1]; As = Symmetric(A);

julia> issymmetric(A)
false

julia> issymmetric(As)
true

julia> A == As
true

Да, я работаю над созданием PR для этого, и есть много такого рода несоответствий. (Я столкнулся с этим не так давно, когда искал все экземпляры transpose , adjoint и conj в коде массива).

Если не указано иное, я буду реализовывать такое поведение, как issymmetric(a) == (a == a.') и ishermitian(a) == (a == a') . Они кажутся довольно интуитивно понятными сами по себе, и существующее использование AFAICT issymmetric предназначено для типов элементов Number (или часто имеет другие ошибки / предположения, которые не имеют большого смысла для вложенных массивов) .

(Альтернативная реализация issymmetric(a) == (a == conj(adjoint(a))) ... не такая "красивая" и не будет работать для массивов "данных" (из String и т.д.), но она совпадает с массивами Number )

Если не указано иное, я буду реализовывать такое поведение, чтобы issymmetric(a) == (a == a.') и ishermitian(a) == (a == a')

Мне кажется правильным решением.

Обновление: передумал. Симметричный, вероятно, в основном для линейной алгебры

Небольшое предложение: часто бывает полезно вычислить сумму по произведению двух векторов ( dotu ), и x.’y возвращающий скаляр, - удобный способ сделать это. Поэтому я был бы за то, чтобы не возвращать Matrix из transpose(::Vector)

Да, это будет RowVector поэтому вы должны получить скаляр. (Обратите внимание, что транспонирование не будет рекурсивным).

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

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

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

Перекрестная публикация https://github.com/JuliaLang/julia/pull/23424#issuecomment -346678279

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

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

Чтобы проиллюстрировать это, рассмотрим mul(A, B) где A и B являются пустыми, с присоединенной оболочкой, с транспонированной оболочкой или с перевернутым массивом Matrix s. Без соединяя транспонирования и массив-флип, A может быть Matrix , присоединенная обернутый Matrix , или транспонирование обернутый Matrix (и аналогично B ). Итак, mul(A, B) нужно поддерживать девять комбинаций типов. Но при объединении транспонирования и переворота массива A может быть Matrix , Matrix с присоединенной оболочкой, Matrix с транспонированной оболочкой или массивом- перевернутый Matrix (и аналогично B ). Итак, теперь mul(A, B) нуждается в поддержке шестнадцати комбинаций типов.

Эта проблема экспоненциально усугубляется с увеличением количества аргументов. Например, без объединения mul!(C, A, B) нужно поддерживать двадцать семь комбинаций типов, тогда как при объединении mul!(C, A, B) нужно поддерживать шестьдесят четыре сочетания типов. И, конечно же, добавление в микс Vector s и не Matrix типов матриц / операторов еще больше усложняет ситуацию.

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

Этот пост объединяет / рассматривает недавнее обсуждение github, Slack и сортировки, а также анализирует возможные пути продвижения вперед. (Духовный преемник https://github.com/JuliaLang/julia/issues/20978#issuecomment-315902532 рожден размышлениями о том, как добраться до 1.0.)

Речь идет о трех семантически различных операциях:

  • сопряженный (линейно-алгебраический, рекурсивный, по умолчанию идеально ленивый)
  • транспонировать (линейно-алгебраический, рекурсивный, в идеале ленивый по умолчанию)
  • array-flip (abstract-array-ic, нерекурсивный, в идеале «ленивый» по умолчанию)

Статус этих операций на мастере

  • Смежное называется adjoint / ' (но оно активно, кроме ' -влекающих выражений, специально пониженных до вызовов A[c|t]_(mul|rdiv|ldiv)_B[c|t][!] , которые избегают промежуточных нетерпеливых сопряжений / транспозиций) .

  • Транспонирование называется transpose / .' (с той же оговоркой, что и adjoint ).

  • Переворот массива называется permutedims(C, (2, 1)) для двумерного C и reshape(C, 1, length(C)) для одномерного C .

Актуальные вопросы

  1. 5332: Специальное понижение до A[c|t]_(mul|rdiv|ldiv)_B[c|t] и связанная комбинаторная коллекция имен методов должны исчезнуть на 1.0. Удаление этого специального понижения / связанных имен методов требует ленивого присоединения и транспонирования.

  2. 13171: Сочетание (ур. ctranspose ) и транспонирование ранее объединялись с переворотом массива с помощью общих резервных вариантов, таких как transpose(x::Any) = x , ctranspose(x::Any) = conj(x) и conj(x::Any) = x . Эти резервные варианты привели к тому, что операции с [c]transpose для некоторых определяемых пользователем числовых типов завершились с ошибкой (возвращают неверные результаты) при отсутствии специализации [c]transpose для этих типов. Тихий возврат неверных результатов - плохая новость, поэтому эти запасные варианты были удалены (# 17075). Было бы здорово не вводить более тихих неудач.

  3. 17075, # 17374, # 19205: удаление предыдущих резервных вариантов сделало переворот массива менее удобным. И в сочетании с (извиняюсь, моя вина) менее чем хорошими предупреждениями об устаревании, это удаление (временно?) Вызвало жалобы. Было бы неплохо более удобное заклинание для переворота массива.

  4. 21037: Было бы замечательно удалить сбивающий с толку синтаксис .' для 1.0. Специальное понижение, упомянутое выше, должно быть удалено, чтобы включить это изменение.

Разработайте цели для решения этих проблем

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

Предложения по дизайну

Поле было сокращено до двух дизайнерских предложений:

  1. Вызов сопряженного adjoint , транспонирование conjadjoint ("сопряженное сопряженное") и переворот массива transpose . adjoint и conjadjoint живут в LinAlg , а transpose живет в Base .

  2. Вызовите присоединенный adjoint , транспонируйте transpose и переверните массив flip . adjoint и transpose живут в LinAlg , а flip живут в Base .

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

Отличия, общий взгляд

  1. Сложность:

    Предложение первое, вызывая array-flip transpose , заставляет LinAlg обрабатывать array-flip в дополнение к транспонированию и присоединению. Следовательно, LinAlg должен существенно расширить набор комбинаций типов, поддерживаемых общими операциями; https://github.com/JuliaLang/julia/pull/23424#issuecomment -346678279 иллюстрирует эту дополнительную сложность в качестве примера, а следующее обсуждение неявно подтверждает существование этой дополнительной сложности.

    Предложение два требует LinAlg поддержки только транспонирования и сопряженный, что LinAlg делает сейчас.

  2. Поломка:

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

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

  3. Связь:

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

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

  4. Тихий или громкий сбой:

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

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

  5. .' : основным аргументом в пользу отказа от поддержки .' является длина transpose . Первое предложение заменяет .' именами transpose и conjadjoint , что не улучшает эту ситуацию. Напротив, второе предложение содержит имена flip и transpose , улучшая эту ситуацию.

Различия в путях до 1.0

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

Путь к 1.0 по второму предложению

  1. Введите ленивое присоединение и транспонирование типов оболочки в LinAlg , например Adjoint и Transpose . Представьте методы mul[!] / ldiv[!] / rdiv[!] отправляемые по этим типам оболочки и содержащие код соответствующих методов A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] . Реализуйте последние методы как короткие потомки первых методов.

    Этот шаг ничего не дает, и сразу же позволяет удалить специальное понижение и исключение .' :

  2. Удалите специальное понижение, которое дает A[c|t]_{mul|ldiv|rdiv}_B[c|t] , вместо этого просто понижайте ' / .' до Adjoint / Transpose ; ранее специально заниженные выражения, дающие вызовы A[c|t]_{mul|ldiv|rdiv}_B[c|t] затем вместо этого становятся эквивалентными вызовами mul / ldiv / rdiv . Устарели A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] для соответствующих методов mul[!] / ldiv[!] / rdiv[!] . Устарела поддержка .' .

    Эти шаги можно было сделать в 0.7. Они нарушают только две вещи: (1) Код, основанный на специальном понижении для попадания в A[c|t]_{mul|ldiv|rdiv}_B[c|t] методов для не Base / LinAlg типов, не работает. Такой код будет выдавать явные MethodError s, указывающие на то, что дает новое понижение / на что нужно перенести сломанный код. (2) Код, основанный на строгом поведении изолированного ' s / .' s, сломается. Типичный режим отказа также должен быть явным MethodError s. Вокруг поломка узкая и громкая.

    И это все, что касается изменений, строго необходимых для версии 1.0.

    На этом этапе Adjoint(A) / Transpose(A) даст ленивое сопряжение и транспонирование, а adjoint(A) / transpose(A) даст активное сопряжение и транспонирование. Последние имена могут оставаться на неопределенный срок или, при желании, быть устаревшими для другого написания в 0.7, например eagereval(Adjoint(A)) / eagereval(Transpose(A)) написание по модулю eagereval или eageradjoint(A) / eagertranspose(A) . В случае устаревания adjoint / transpose можно было бы перепрофилировать в 1.0 (хотя с Adjoint(A) / Transpose(A) вокруг, я не уверен, будет ли это необходимо).

    В заключение...

  3. Введите flip и / или Flip в Base . Это изменение может быть добавлено в 1.x при необходимости.

Путь к 1.0 по первому предложению

А что насчет первого предложения? Ниже описаны два возможных пути. Первый путь объединяет изменения в 0.7, но предполагает тихую поломку. Второй путь позволяет избежать бесшумной поломки, но требует большего изменения 0,7-> 1,0. Обе схемы постоянно развивают кодовую базу; менее непрерывные эквиваленты могут объединить / избежать некоторой работы / оттока, но, вероятно, будут более сложными и подверженными ошибкам. Текущая работа, похоже, идет по первому пути.

Первый путь по первому предложению (с бесшумной поломкой)
  1. Измените семантику transpose с transpose на array-flip, а также обязательно семантику всех функциональных возможностей, связанных с transpose . Например, все методы A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] начинающиеся с At_... или заканчивающиеся ..._Bt[!] потенциально нуждаются в семантической доработке. Также необходимо изменить, например, определения и поведение Symmetric / issymmetric . Переместите сам transpose в Base .

    Эти изменения будут тихо и широко нарушены.

  2. Введите conjadjoint в LinAlg . Этот шаг требует восстановления всех методов, затронутых на предыдущем шаге, но в их исходной семантической форме, и теперь с другими именами, связанными с conjadjoint (скажем, Aca_... и ..._Bca[!] names) . Также требуется добавить методы для дополнительных комбинаций типов, которые одновременно поддерживают переворот массива (теперь transpose ), транспонирование (теперь conjadjoint ) и присоединение в LinAlg (например, ca вариантов среди A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!] ).

  3. Введите ленивое сопряжение и транспонирование (называемое conjadjoint ) в LinAlg , скажем, Adjoint и ConjAdjoint . Представьте ленивый тип оболочки с переворотом массива (называемый transpose ) в Base , скажем, Transpose . Представьте методы mul[!] / ldiv[!] / rdiv[!] отправляемые по этим типам оболочки и содержащие код соответствующих методов A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!] . Реализуйте последние методы как короткие потомки первых методов.

  4. Удалите специальное понижение, которое дает A[c|t]_{mul|ldiv|rdiv}_B[c|t] , вместо этого просто понижайте ' / .' до Adjoint / Transpose ; ранее специально заниженные выражения, дающие вызовы A[c|t]_{mul|ldiv|rdiv}_B[c|t] затем вместо этого становятся эквивалентными вызовами mul / ldiv / rdiv (хотя помните, что семантика будет незаметно изменена). Устарело использование A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] для соответствующих методов mul[!] / ldiv[!] / rdiv[!] . Аналогичным образом удалите недавно представленные методы Aca_... / ...Bca[!] в пользу эквивалентов mul[!] / ldiv[!] / rdiv[!] . Устарела поддержка .' .

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

На этом этапе Adjoint(A) / Transpose(A) / ConjAdjoint(A) соответственно приведет к ленивому присоединению, переворачиванию массива и транспонированию, а также adjoint(A) / transpose(A) / conjadjoint(A) соответственно приведет к активному присоединению, переворачиванию массива и транспонированию. Последние имена могут оставаться на неопределенный срок или, при желании, заменяться на другое написание также в 0.7 (см. Выше).

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

Второй путь по первому предложению (предотвращение бесшумной поломки)
  1. Представьте нетерпеливого conjadjoint в LinAlg . Перенести все функции и методы, в настоящее время связанные с transpose (включая, например, A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] методы, включающие t ) в conjadjoint и производные имена. Сделайте все имена, связанные с transpose короткими дочерними элементами новых эквивалентов conjadjoint . Принизить все transpose о связанных именах conjadjoint эквивалентов.

  2. Введите ленивые сопряженные и транспонированные (называемые conjadjoint ) типы оболочки в LinAlg , скажем, Adjoint и ConjAdjoint . Представьте методы mul[!] / ldiv[!] / rdiv[!] отправляемые по этим типам оболочки и содержащие код соответствующих методов A[c|ca]_{mul|ldiv|rdiv}_B[c|ca][!] . Реализуйте последние методы как короткие потомки первых методов.

  3. Введем в Base ленивый тип оболочки с переворачиванием массива, который называется Transpose (несколько сбивает с толку, поскольку одновременно transpose устарел до conjadjoint с семантикой транспонирования). Добавьте все общие методы, необходимые для работы array-flip ( Transpose ), в Base . Затем добавьте методы в LinAlg для всех дополнительных комбинаций типов, которые одновременно поддерживают переворот массива (теперь Transpose , но не transpose ), транспонирование (теперь conjadjoint и ConjAdjoint ), а для присоединения в LinAlg требуется (например, mul[!] / rdiv[!] / ldiv[!] эквиваленты A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!] которые в настоящее время не существуют).

  4. Удалите специальное понижение, которое дает A[c|t]_{mul|ldiv|rdiv}_B[c|t] , вместо этого просто понижайте ' / .' до Adjoint / Transpose ; ранее специально заниженные выражения, дающие вызовы A[c|t]_{mul|ldiv|rdiv}_B[c|t] вместо этого становятся эквивалентными вызовами mul / ldiv / rdiv . Устарело использование A[c|t]_{mul|ldiv|rdiv}_B[c|t][!] для соответствующих методов mul[!] / ldiv[!] / rdiv[!] . Устарела поддержка .' .

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

В этот момент Adjoint(A) / Transpose(A) / ConjAdjoint(A) соответственно приведет к ленивому присоединению, переворачиванию массива и транспонированию. adjoint(A) / conjadjoint(A) соответственно даст нетерпеливое присоединение и транспонирование. transpose(A) будет устаревшим, чтобы conjadjoint ; transpose желании adjoint можно оставить на неопределенное время или отказаться от его использования в пользу другого написания в 0.7. Другой способ написания conjadjoint может быть прямо в 0.7.

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

#

Спасибо за прочтение!

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

Я попытался прочитать подробные обсуждения выше, но не смог понять, в какой момент было решено / мотивировано, что математический transpose должен быть рекурсивным. Казалось, что это следует из некоторой частной дискуссии (где-то между 14 и 18 июля), после которой внезапно было введено математическое и структурное транспонирование.
@ Sacha0 описывает это как

Как это возникло: раньше "структурное транспонирование" объединялось с "математическим сопряженным" / "математическим транспонированием".

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

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

Спасибо @ Sacha0 за отличную запись. Я поддерживаю предложение 2 (вызовите присоединенный adjoint , переместите transpose и переверните массив flip . adjoint и transpose живут в LinAlg , а flip живет в Base .). Мне кажется, что это дает лучший конечный результат (что должно быть основной проблемой), а также позволяет сделать более чистый способ для версии 1.0. Я согласен с вашими пунктами выше, поэтому я не буду их повторять, но вот несколько дополнительных комментариев.

Один из аргументов в пользу нерекурсивного transpose состоит в том, что .' -синтаксис очень удобен и, таким образом, может использоваться, например, для Matrix{String} . Предполагая, что синтаксис уходит (# 21037) и нужно использовать transpose(A) вместо A.' , тогда я думаю, что flip -функция была бы желанным дополнением (короче и понятнее, чем transpose(A) , и определенно лучше, чем permutedims(A, (2,1)) ).

Разделение между LinAlg и Base которое дает предложение 2, также очень хорошо. Не только потому, что LinAlg нужно заботиться только о том, как обрабатывать Transpose и Adjoint , но также и то, что это дает понять, что мы рассматриваем transpose и adjoint - это LinAlg операции, а flip - общая AbstractMatrix вещь, которая просто переворачивает размеры матрицы.

Наконец, я не видел особых жалоб на неработающие transpose(::Matrix{String}) т. Д. Это действительно обычный вариант использования? В большинстве случаев лучше построить перевернутую матрицу с самого начала.

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

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

@Jutho Это тот же аргумент, что и сопряженный (сопряженный комплексного числа является его сопряженным (поскольку комплексные числа являются допустимыми векторными пространствами ранга 1 и линейными операторами, применяются внутреннее произведение и сопряженные концепции), а также сопряженная блочная матрица рекурсивно ...). Например, люди могут захотеть заниматься линейной алгеброй с вложенными реальными матрицами. Я до сих пор вижу много кода, в котором .' для «сопряженной вещественной матрицы или вектора», и я тоже делал это. Здесь ' будет не только действительным, но и более общим (работает с массивами, которые могут быть комплексными и / или вложенными). Более реальное использование рекурсивного транспонирования для вложенных сложных матриц происходит, когда ваши уравнения дают вам conj(adjoint(a)) , что относительно редко, но все же случается (это достаточно редко, и я счастлив, если с ним не связана функция - в обсуждения выше и в других местах разные люди начали называть это разными вещами, такими как «математическое транспонирование», я выбрал conjadoint , но ИМО это не лучшая терминология, кроме, возможно, самого transpose ). В линейной алгебре нерекурсивная операция, которая переупорядочивает блоки блочной матрицы, но ничего не делает с самими блоками, обычно не появляется.

Наконец, я не встречал особых жалоб на то, что transpose(::Matrix{String}) не работают. Это действительно обычный вариант использования? В большинстве случаев лучше построить перевернутую матрицу с самого начала.

Я думаю, что такие вещи весьма желательны для работы Джулии в сфере радиовещания. Например, довольно часто возникает желание взять внешнее (декартово) произведение некоторых данных и, возможно, отобразить его через функцию. Поэтому вместо чего-то вроде map(f, product(a, b)) мы можем использовать broadcast(f, a, transpose(b)) или просто f.(a, b.') . В нескольких персонажах много силы.

Мои мысли: чтобы избежать добавления еще большего количества имен функций в это пространство (например, flip ), мне интересно, могли бы мы иметь значение по умолчанию для перестановки в permutedims(a) = permutedims(a, (2,1)) . К сожалению, это не так коротко, как flip , но значительно менее неприятно, чем полная форма permutedims(a, (2,1)) (и имеет побочный эффект обучения пользователей более общей функции).

( @ Sacha0 Большое спасибо за вашу рецензию. К вашему сведению, выбор между Adjoint и Transpose wrappers или RowVector + MappedArray + что угодно для перевернутого матрицы, как планировалось ранее (может быть, всего лишь PermutedDimsArray ), я все еще склоняюсь к последнему ...)

Более реальное использование рекурсивного транспонирования для вложенных сложных матриц происходит, когда ваши уравнения дают вам conj(adjoint(a))

Как только вы получите conj(adjoint(a)) в своих уравнениях, как вы узнаете, что на самом деле это conj которое вы хотели применить к элементам матрицы, а, возможно, не adjoint , то есть, возможно, вы действительно хотите adjoint.(adjoint(a)) .

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

Сопряженный вектор должен быть линейным оператором, отображающим его на скаляр. То есть, a '* a должно быть скаляром, если a - вектор.

несовместимо с тем, как работает Julia Base: сделайте a=[rand(2,2),rand(2,2)] и соблюдайте a'*a . Это не скаляр. Итак, является ли теперь a матрицей, потому что это вектор, заполненный матрицами? Вышеупомянутое является скаляром только в том случае, если вы действительно намеревались использовать кольцо матриц 2x2 в качестве скаляров, над которыми вы определили модуль. Но в этом контексте мне не совсем ясно, что приведенный выше результат является ожидаемым. Евклидов внутренний продукт в любом случае редко используется в контексте модулей и т. Д., Поэтому я не думаю, что Base даже следует беспокоиться о том, чтобы определять правильное поведение для него.

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

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

  • Сколько людей на самом деле используют / полагаются на текущее рекурсивное поведение adjoint для какой-либо практической работы?
  • Существуют ли другие языки, использующие такой рекурсивный подход? (Matlab, использующий ячейки матриц, не делает)

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

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

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

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

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

Сделайте a=[rand(2,2),rand(2,2)] и соблюдайте a'*a . Это не скаляр.

В этом случае «скалярное кольцо» a - это кольцо матриц 2x2. Так что это лингвистическая проблема, а не проблема того, как работает adjoint.

Спор о том, как a'*a должен работать, от того, как он сейчас работает (или не работает) в Julia, кажется довольно замкнутым.

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

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

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

https://github.com/KristofferC/BlockArrays.jl/ Участники приветствуются :)

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

@StefanKarpinski : Пример скалярного кольца в точности противоречит рекурсии: являются ли сами матрицы 2x2 скалярами, или определение является рекурсивным и является ли базовый тип Number скаляром?

В первом случае у вас фактически есть модуль вместо векторного пространства, и это, вероятно, зависит от варианта использования, хотите ли вы conj или adjoint на этих `` скалярах '', если бы вы даже были используя внутренние продукты и прилегающие вообще.

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

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

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

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

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

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

Позволь мне объяснить. То, с чем я в целом согласен

  1. Скаляры - допустимые линейные операторы ранга 1. Учитывая мощную систему типов Джулии, нет причин, по которым концепции LinAlg такие как adjoint , не применимы, например, мы должны определить adjoint(z::Complex) = conj(z) . (Помимо векторов и матриц скаляров, концепции LinAlg также могут быть расширены (я полагаю, пользователями) на другие объекты - @stevengj упомянул, например, векторные пространства бесконечного размера (гильбертовы пространства)).
  2. Мы должны уметь каким-то образом иметь дело со скалярами с разными представлениями. Прототипный пример здесь - комплексное число z = x + y*im можно смоделировать как Z = x*[1 0; 0 1] + y*[0 1; -1 0] и операции + , - , * , / этом изоморфизме сохраняются \ . (Обратите внимание, однако, что conj(z) переходит в adjoint(Z) / transpose(Z) / flip(Z) - подробнее об этом позже).
  3. Каким-то образом должны быть возможны блочные матрицы (текущий подход по умолчанию основан на рекурсивном adjoint и т. Д.).

Кажется разумным, что Base.LinAlg будет совместим с 1 и 2, но IMO 3 следует выполнять только в Base если он подходит естественным образом (в противном случае я бы предпочел использовать внешние пакеты, такие как https: / /github.com/KristofferC/BlockArrays.jl).

Теперь я понимаю, что мы объединяем 2 и 3, и это приводит к некоторым несоответствиям ( @Jutho тоже указал на это). Ниже я хочу показать, что 3. использование рекурсивных adjoint и других операций с блочными матрицами не дает нам 2. Самый простой способ - на примере. Давайте определим матрицу комплексных чисел 2x2 как m = [z1 z2; z3 z4] , изоморфное представление M = [Z1 Z2; Z3 Z4] и стандартную блочную матрицу 2x2 b = [m1 m2; m3 m4] где m1 т. Д. Являются квадратами одинакового размера. матрицы Number . Семантически правильные ответы на общие операции перечислены ниже:

| операция | z | Z | m | M | b |
| - | - | - | - | - | - |
| + , - , * | рекурсивный | рекурсивный | рекурсивный | рекурсивный | рекурсивный |
| conj | conj(z) | Z' или Z.' или flip(Z) | conj.(m) | adjoint.(M) (или transpose.(M) ) | conj.(b) |
| adjoint | conj(z) | Z' или Z.' или flip(Z) | flip(conj.(m)) или рекурсивный | flip(transpose.(m)) или рекурсивный | рекурсивный |
| trace | z | Z | z1 + z4 | Z1 + Z4 | trace(m1) + trace(m4) |
| det | z | Z | z1*z4 - z2*z3 | Z1*Z3 - Z2*Z3 | det(m1) * det(m4 - m2*inv(m1)*m3) (если m1 обратимый, см., Например, Википедию ) |

Принимая во внимание такие операции, как trace и det которые возвращают скаляры, я думаю, довольно ясно, что наша система типов Julia для LinAlg никак не могла справиться с встраиванием матрицы 2x2 Complex "автоматическим" способом, предполагая, что мы имеем в виду под словом "скаляр" в любой конкретный момент. Ярким примером является trace(Z) где Z = [1 0; 0 1] - это 2 , а это наше представление 1 . Аналогично для rank(Z) .

Одним из способов восстановления согласованности является явное определение нашего представления 2x2 как скалярного, например, путем подтипа Number как показано ниже:

struct CNumber{T <: Real} <: Number
    m::Matrix{T}
end
CNumber(x::Real, y::Real) = CNumber([x y; -y x])

+(c1::CNumber, c2::CNumber) = CNumber(c1.m + c2.m)
-(c1::CNumber, c2::CNumber) = CNumber(c1.m - c2.m)
*(c1::CNumber, c2::CNumber) = CNumber(c1.m * c2.m)
/(c1::CNumber, c2::CNumber) = CNumber(c1.m / c2.m)
\(c1::CNumber, c2::CNumber) = CNumber(c1.m \ c2.m)
conj(c::CNumber) = CNumber(transpose(c.m))
zero(c::CNumber{T}) where {T} = CNumber(zero(T), zero(T))
one(c::CNumber{T}) where {T} = CNumber(one(T), one(T))

С этим определением общие методы LinAlg , вероятно, будут работать нормально.

Вывод, который я делаю из этого: рекурсивный adjoint - это удобство для блочных матриц и векторов векторов. Он не должен быть мотивирован математической корректностью для типов "скалярных" матриц 2x2, которые я пометил выше как Z . Я считаю, что это наш выбор, поддерживаем ли мы блочные матрицы или нет по умолчанию, со следующими плюсами и минусами:

Плюсы

  • Удобство для пользователей блочного массива
  • Векторы векторов являются допустимыми векторными пространствами, а блочные матрицы - допустимыми линейными операторами при + , * , conj и т. Д. Если возможно сделать это естественным изоморфизмом (в отличие от приведенного выше примера Z , который требует CNumber ), тогда почему бы и нет?

Минусы

  • Существенная дополнительная сложность нашей реализации LinAlg (которая потенциально может находиться в другом пакете).
  • Немного сложно (но не невозможно) поддерживать такие вещи, как eig(block_matrix) . Если мы говорим, что LinAlg поддерживает eig а LinAlg поддерживает блочные матрицы, то я считаю это ошибкой, пока она не будет исправлена. Учитывая большой объем функциональных возможностей, предоставляемых LinAlg , трудно понять, что мы когда-либо будем «закончены».
  • Неудобство пользователей данных, желающих использовать такие операции, как transpose нерекурсивным способом,

Для меня вопрос в том, хотим ли мы сказать, что по умолчанию LinAlg ожидает, что элементы AbstractArray s будут "скалярными" (подтипы или утиные типы Number ) и отдать сложность массивов блоков внешним пакетам? Или мы принимаем сложность в Base и LinAlg ?

План до дня назад был последним.

@ Sacha0 Я пытался выполнить необходимую RowVector работу для поддержки изменений здесь (более ранние: нерекурсивные transpose , RowVector вспомогательные строки и другие данные) и теперь мне интересно, что вы имели в виду с точки зрения дизайна.

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

| | Вектор | Матрица |
| - | - | - |
| adjoint | RowVector с рекурсивным adjoint | AdjointMatrix или TransposedMatrix с рекурсивным adjoint |
| transpose | RowVector с рекурсивным transpose | TransposeMatrix с рекурсивным transpose |
| flip | копия или PermutedDimsArray ? | копия или PermutedDimsArray ? |
| conj из AbstractArray | ленивый или нетерпеливый? | ленивый или нетерпеливый? |
| conj из RowVector или TransposedMatrix | ленивый | ленивый |

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

Итак, несколько основных вопросов, которые меня отвлекут:

  • Мы делаем рекурсивные transpose или нет? А как насчет adjoint ?
  • Если да, то продолжим ли мы предполагать conj(transpose(array)) == adjoint(array) ?
  • Кажется, что по крайней мере некоторые сложные сопряжения будут ленивы, например, для поддержки всех текущих операций BLAS без дополнительных копий. Делаем ли мы conj ленивыми для всех массивов?
  • Если мы введем flip , это будет лениво или нетерпеливо?

К вашему сведению, я пробовал подход с низким коэффициентом трения к "переворачиванию" размеров матрицы в # 24839, используя более короткую форму для permutedims .

Я решительно поддерживаю предложение @Sacha0 2. Мы не полностью разобрали теоретические основы рекурсивного и нерекурсивного сопряжения, но независимо от этого: то, как оно сейчас, оказалось полезным, по крайней мере для меня , и отходить от этого в последнюю минуту, возможно, не рекомендуется. Транспонирование должно следовать за сопряженным (= conj∘adjoint ) в этом отношении, если это вообще необходимо.

FWIW, Mathematica не выполняет рекурсивные Transpose или ConjugateTranspose :

untitled

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

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

объясняя, почему adjoint ... должен быть рекурсивным по определению ...

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

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

См., Например, https://github.com/JuliaLang/julia/issues/20978#issuecomment-347777577 и более ранние комментарии :). Лучший!

Аргумент в пользу рекурсивного присоединения довольно прост в определениях. dot(x,y) должен быть внутренним продуктом, т.е. производить скаляр, а определение сопряженного таково: dot(A'*x, y) == dot(x, A*y) . Рекурсия (как для dot и для adjoint ) следует из этих двух свойств.

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

Обратите внимание, что одна проблема с использованием flip для нерекурсивного транспонирования заключается в том, что и Matlab, и Numpy используют flip для того, что мы называем flipdim .

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

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

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

Это покажется немного окольным, так что потерпите меня :).

adjoint Джулии относится именно к эрмитово сопряженным . В общем, для полных нормированных векторных пространств U и V (банаховых пространств) с соответствующими двойственными пространствами U * и V и для линейного отображения A: U -> V сопряженное к A, обычно обозначаемое A , является линейным отображением A *: V * -> U * . То есть, в общем случае сопряженное - это линейная карта между двойственными пространствами, так же как в общем случае транспонированная A ^ t является линейной картой между двойственными пространствами, как упомянуто выше. Так как же согласовать эти определения с известным понятием эрмитово сопряженного? :)

Ответ кроется в дополнительной структуре пространств, в которых обычно работают, а именно в полных пространствах внутреннего продукта (гильбертовых пространствах). Внутреннее произведение индуцирует норму, поэтому полное внутреннее произведение (гильбертово) является полным нормированным пространством (банахово) и тем самым поддерживает концепцию (эрмитова) сопряженного. Вот ключ: имея внутренний продукт, а не просто норму, применима одна из самых красивых теорем линейной алгебры, а именно теорема Рисса о представлении. В двух словах, теорема о представлении Рисса утверждает, что гильбертово пространство естественно изоморфно своему сопряженному пространству. Следовательно, при работе с гильбертовыми пространствами мы обычно идентифицируем пространства и их двойники и опускаем различие. Осуществляя эту идентификацию, вы приходите к знакомому понятию эрмитово сопряженного соединения как A *: V -> U, а не A *: V * -> U * .

И такая же идентификация обычно выполняется для транспонирования при рассмотрении гильбертовых пространств, так что также A ^ t: V -> U, что дает знакомое понятие транспонирования. Итак, чтобы прояснить, да, общее понятие транспонирования - это общее понятие транспонирования, применяемое к наиболее знакомой (гильбертовой) настройке, так же, как общее понятие эрмитова сопряженного является общим понятием сопряженного, применяемым к этому параметру. Лучший!

Прошу прощения за избиение мертвой лошади, но я подумал, что вкратце резюмирую проблемы математической неразберихи по-другому. Ключевой момент разногласий заключается в том, как математически интерпретировать объект Vector{T} когда T - это не просто подтип Number без подструктуры, подобной массиву.

Одна школа мысли принимает утверждение @stevengj о том, что

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

По модулю некоторых тонкостей, касающихся бесконечномерных векторных пространств и т. Д., Это в основном означает, что мы должны думать о векторах векторов как о блочных векторах. Итак, Vector{Vector{T<:Number}} нужно мысленно «сплющить» до простого Vector{T} . В рамках этой парадигмы линейные операторы, представленные в виде матриц матриц, следует аналогичным образом рассматривать как блочные матрицы, и adjoint безусловно, должны быть рекурсивными, как и transpose если мы используем слово в математический смысл . Пожалуйста, поправьте меня, если я ошибаюсь, но я считаю, что люди, которые придерживаются этой точки зрения, думают, что что-то вроде Vector{Matrix{T}} не имеет достаточно естественной интерпретации, которую мы должны разработать для этого. (В частности, мы не должны просто предполагать, что внутренний Matrix является матричным представлением комплексного числа, потому что, как сказал @stevengj ,

Если вы представляете комплексные числа матрицами 2x2, у вас другое комплексное векторное пространство.

)

Другая школа мысли заключается в том, что Vector{T} всегда следует рассматривать как представление вектора в абстрактном векторном пространстве над скалярами (в смысле слова линейной алгебры) типа T , независимо от типа T . В этой парадигме Vector{Vector{T'}} следует рассматривать не как элемент пространства прямой суммы, а как вектор над скалярами Vector{T'} . В этом случае transpose(Matrix{T}) не должно быть рекурсивным, а должно просто перевернуть внешнюю матрицу. Одна проблема с этой интерпретацией заключается в том, что для элементов типа T чтобы сформировать допустимое кольцо скаляров, должно быть четко определенное понятие (коммутативного) сложения и умножения. Для вектора типа Vector{Vector{T'}} нам понадобится правило для умножения двух "скаляров" Vector{T'} на другой Vector{T'} . Конечно, можно было бы придумать такое правило (например, поэлементное умножение, которое сводит проблему к умножению T' ), но универсального естественного способа сделать это не существует. Другая проблема заключается в том, как adjoint будет работать в этой интерпретации. Эрмитово сопряженное соединение определено только на линейных операторах над гильбертовым пространством, скалярное поле которого по определению должно быть либо действительным, либо комплексным числом. Поэтому, если мы хотим применить adjoint к матрице Matrix{T} , мы должны предположить, что T - это некоторое представление комплексных чисел (или действительных чисел, но я просто придерживаюсь со сложным, потому что этот случай более тонкий). В этой интерпретации adjoint не должно быть рекурсивным, но должно перевернуть внешнюю матрицу, а затем применить conjugate . Но здесь есть больше проблем, потому что правильное действие conjugate(T) зависит от характера представления. Если это представление 2x2, описанное в Википедии, тогда conjugate должно перевернуть матрицу. Но по причинам, описанным выше, conjugate определенно не должно всегда переворачивать матрицы. Так что реализовать такой подход было бы непросто.

Вот мои собственные мысли: не существует «объективно правильного» ответа на вопрос, должен ли transpose быть рекурсивным при применении к массивам, элементы которых имеют еще более сложную подструктуру. Это зависит от того, как именно пользователь выбирает представление своей абстрактной алгебраической структуры в Julia. Тем не менее, поддержка полностью произвольных колец скаляров кажется очень сложной, поэтому я думаю, что из соображений практичности мы не должны пытаться быть такими амбициозными и должны забыть об эзотерической математике модулей над нестандартными кольцами. У нас обязательно должна быть функция в Base (с более простым синтаксисом, чем permutedims(A, (2,1)) ), которая не имеет ничего общего с концепцией транспонирования линейной алгебры и просто переворачивает матрицы и не делает ничего рекурсивного, независимо от того, называется transpose или flip или как. Было бы неплохо, если бы adjoint и отдельная функция транспонирования (возможно, с другим именем) в LinAlg были рекурсивными, потому что тогда они могли бы обрабатывать блочные векторы / матрицы и простую реализацию прямой суммы как векторы векторов, но этого не требует «объективная математическая корректность», и было бы хорошо принять это решение исключительно на основании простоты реализации.

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

Сопряженный Юлии относится именно к эрмитовому соплеменнику. В общем случае для полных нормированных векторных пространств U и V (банаховых пространств) с соответствующими двойственными пространствами U * и V и для линейного отображения A: U -> V эрмитово сопряженное к A, обычно обозначаемое A , является линейным отображением A *: V * -> U *. То есть, в общем случае эрмитово сопряженное пространство является линейным отображением между дуальными пространствами, так же как в общем случае транспонированное A ^ t является линейным отображением между двойственными пространствами, как упомянуто выше. Так как же согласовать эти определения со знакомым понятием эрмитово сопряженного? :)

На самом деле это не так. То, что вы здесь описываете, на самом деле является транспонированием, но (как я уже упоминал) в некоторых полях это называется сопряженным (без эрмитовского) и обозначается как A ^ t или A ^ * (никогда не A ^ dagger). Фактически, он выходит далеко за пределы векторных пространств, и в теории категорий такое понятие существует в любой моноидальной категории (например, в категории Cob n-мерных ориентированных многообразий с кобордизмами как линейные отображения), где оно называется присоединенным сопряжением (в на самом деле может быть два разных A и A , поскольку левое и правое двойственное пространство не обязательно одно и то же). Но учтите, это никогда не связано с комплексным сопряжением. Элементы V * действительно являются линейными отображениями f: V-> Scalar, а для линейного отображения A: U-> V и вектора v из U мы имеем f (Av) = (A ^ tf) (v) . Поскольку действие f не требует комплексного сопряжения, то и определение A ^ t тоже.

Ответ кроется в дополнительной структуре пространств, в которых вы обычно работаете, а именно в полных внутренних пространствах продукта (гильбертовых пространствах). Внутреннее произведение индуцирует норму, поэтому полное внутреннее произведение (гильбертово) является полным нормированным пространством (банаховым) и, таким образом, поддерживает концепцию эрмитова сопряженного. Вот ключ: имея внутренний продукт, а не просто норму, применима одна из самых красивых теорем линейной алгебры, а именно теорема Рисса о представлении. В двух словах, теорема о представлении Рисса утверждает, что гильбертово пространство естественно изоморфно своему сопряженному пространству. Следовательно, при работе с гильбертовыми пространствами мы обычно идентифицируем пространства и их двойники и опускаем различие. Осуществляя эту идентификацию, вы приходите к знакомому понятию эрмитово сопряженного соединения как A *: V -> U, а не A *: V * -> U *.

Опять же, я не думаю, что это полностью правильно. В пространствах внутреннего продукта внутренний продукт представляет собой полуторалинейную форму dot из конъюнктуры con (V) x V -> Scalar (с конъюнктурным векторным пространством con (V)). Это действительно позволяет установить отображение из V в V * (или технически из конъюнктуры (V) в V *), что действительно является теоремой о представлении Рисса. Однако нам это не нужно, чтобы ввести эрмитово сопряженное соединение. Действительно, внутреннего произведения dot достаточно, а эрмитово сопряженное к линейному отображению A таково, что
dot(w, Av) = dot(A' w, v) . Это включает комплексное сопряжение.

На самом деле это не так. То, что вы здесь описываете, на самом деле является транспонированием, но (как я уже упоминал) в некоторых полях это называется сопряженным (без эрмитовского) и обозначается как A ^ t или A ^ * (никогда не A ^ dagger). [...]

@Jutho , пожалуйста, посмотрите, например, страницу в Hermitian adjoint .

Может быть, между разными областями математики есть несоответствия, но:
https://en.wikipedia.org/wiki/Transpose_of_a_linear_map
и в частности
https://en.wikipedia.org/wiki/Transpose_of_a_linear_map#Relation_to_the_Hermitian_adjoint
и несчетное количество ссылок в теории категорий, например
https://arxiv.org/pdf/0908.3347v1.pdf

https://en.wikipedia.org/wiki/Transpose_of_a_linear_map
и в частности
https://en.wikipedia.org/wiki/Transpose_of_a_linear_map#Relation_to_the_Hermitian_adjoint

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

Я также подпишусь под предложением @Sacha0 2. Я тоже permutedims ; Думаю, это лучше, чем flip .

@ Sacha0 , тогда у нас есть другой способ интерпретировать это. Я прочитал это как
Для данного A: U-> V,
transpose (A) = dual (A) = (иногда также) adjoint (A): V * -> U *
эрмитово сопряженный (A) = кинжал (A) = (обычно просто) сопряженный (A): V-> U
и связь между ними точно получается с помощью карты из пространства в двойственное пространство (т.е. Рисса ...), которое включает комплексное сопряжение. Следовательно, эрмитово сопряжение включает спряжение, а транспонирование - нет.

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

Эрмитово сопряженное к A, обычно обозначаемое A , является линейным отображением A : V * -> U *. То есть, в общем случае эрмитово сопряженное соединение является линейным отображением между двойственными пространствами, так же как в общем случае транспонированное A ^ t является линейным отображением между двойственными пространствами.

Итак, являются ли транспонированные и эрмитово сопряженные два разных способа преобразования A: U-> V в карту из V -> U ? Я был бы рад продолжить обсуждение этого вопроса, но я думаю, нам лучше сделать это в другом месте. Но на самом деле, свяжитесь со мной, поскольку мне очень интересно узнать об этом больше.

Также см. Http://staff.um.edu.mt/jmus1/banach.pdf для ссылки, что сопряженный, используемый в контексте банаховых пространств, действительно транспонирован, а не эрмитово сопряженный (в частности, это линейный, а не антилинейный трансформация). Википедия (и другие ссылки) действительно объединяют эти два понятия, используя понятие эрмитова сопряженного в гильбертовых пространствах в качестве мотивации для обобщенного определения сопряженного в банаховых пространствах. Однако последний действительно транспонирован (и не требует внутреннего продукта или нормы). Но это транспонирование, о котором я говорил, что никто на самом деле не использует в компьютерном коде.

Относительно Джулии Бэйс: я не против рекурсивного эрмитовского спряжения; Я согласен, что часто это будет правильным поступком. Я просто не уверен, что Base следует пытаться делать умные вещи, когда тип элемента не Number . Даже если T - это число, в Base нет поддержки для гораздо более распространенного использования неевклидовых внутренних продуктов (и связанных с ними модифицированных определений сопряженных), и я не думаю, что этого должно быть. Итак, я думаю, что основной мотивацией были блочные матрицы, но я просто думаю, что тип специального назначения (в Base или в пакете) намного больше юлианского, и, как также упомянул @andyferris , он не похож на все остальные LinAlg поддерживает это понятие блочных матриц, даже таких простых вещей, как inv (не говоря уже о факторизации матриц и т. д.).

Но если рекурсивное эрмитово спряжение никуда не денется (меня это устраивает), то я думаю, что для согласованности dot и vecdot должны действовать рекурсивно с элементами. В настоящее время это не так: dot вызывает x'y для элементов (что не то же самое, когда элементы являются матрицами), а vecdot вызывает dot по элементам. Таким образом, для вектора матриц на самом деле нет способа получить скалярный результат. Я был бы счастлив подготовить PR, если люди согласятся, что текущая реализация на самом деле не противоречит рекурсивному adjoint .

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

Triage считает, что @ Sacha0 следует продолжить работу над предложением 2, чтобы мы могли его опробовать.

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

1 - для LinAlg , AbstractVector v - это length(v) -мерный вектор с (скалярными) базисными весами v[1] , v[2] , ..., v[length(v)] .

(и аналогично для AbstractMatrix ).

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

Наш текущий подход больше похож на:

2 - для LinAlg , AbstractVector v является прямой суммой абстрактных векторов length(v) . Мы также включаем достаточно определений для скалярных типов, таких как Number чтобы до LinAlg они были допустимыми одномерными линейными операторами / векторами.

и аналогично для (блочных) матриц. Это гораздо более обобщенно, чем реализации линейной алгебры в MATLAB, numpy, eigen и т. Д., И это отражение мощной системы типов / диспетчеризации Джулии, что это даже возможно.

Основная причина, по которой я считаю вариант 2 желательным, снова заключается в том, что система типа / отправки Джулии позволяет нам иметь гораздо более широкую цель, которая расплывчато выглядит примерно так:

3 - В LinAlg мы пытаемся создать общий интерфейс линейной алгебры, который работает для объектов, удовлетворяющих линейности (и т. Д.) В + , * , conj (и т. д.), обрабатывая такие объекты как линейные операторы / члены гильбертова пространства / что угодно, если это необходимо.

Это действительно крутая цель (безусловно, намного превосходящая любой другой язык программирования / библиотеку, о которых я знаю), полностью мотивирует рекурсивные adjoint и 2 (потому что + , * и conj сами рекурсивный) и поэтому @ Sacha0 «s предложение 2 и решение сортировки является выбором хорошо :)

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

Ура, давайте делать! С нетерпением жду продолжения общения в автономном режиме :). Лучший!

Хорошее резюме Энди! :)

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

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

  • Его могут использовать все люди, работающие с матрицами, даже содержащими нечисловые данные. Таким же образом они, вероятно, узнают об этой операции и будут ее искать, используя другие языки и основную математику, экстраполированную на их нечисловые варианты использования.
  • Не нужно писать дополнительный код, чтобы ленивый тип flip или PermutedDimsArray взаимодействовал с LinAlg . Что, если у меня есть числовая матрица, которую я перевернул, а не транспонировал; Смогу ли я умножить его на другие матрицы (желательно с помощью BLAS)?

  • С нерекурсивным transpose и рекурсивным adjoint мы можем легко получить нерекурсивное сопряженное соединение как conj(transpose(a)) и рекурсивное транспонирование conj(adjoint(a)) . И все же с LinAlg все будет нормально взаимодействовать.

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

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

A = Array[1 &, {2, 3, 4, 5}];
Dimensions[A]  # returns {2, 3, 4, 5}
Dimensions[Transpose[A]] # returns {3, 2, 4, 5}

РЕДАКТИРОВАТЬ: Ой, это также было прокомментировано выше, извините

Так что я запутался. Казалось, существует довольно твердое мнение о том, что transpose следует сделать нерекурсивным - например, https://github.com/JuliaLang/julia/issues/20978#issuecomment -285865225, https://github.com/ JuliaLang / julia / issues / 20978 # issuecomment -285942526, https://github.com/JuliaLang/julia/issues/20978#issuecomment -285993057, https://github.com/JuliaLang/julia/issues/20978#issuecomment - 348464449 и https://github.com/JuliaLang/julia/pull/23424. Затем @ Sacha0 представил два предложения, одно из которых оставит транспонирование рекурсивным, но представит нерекурсивную функцию flip , которая получила сильную поддержку, несмотря на (насколько я могу судить) на самом деле не рассматривалась как возможность раньше . Затем @JeffBezanson предположил, что нам не понадобится flip конце концов, если мы дадим permutedims второй аргумент по умолчанию, который также получил сильную поддержку.

Итак, теперь, похоже, единодушное мнение заключается в том, что единственные реальные изменения в transpose должны быть «за кулисами»: в отношении особого снижения и ленивых и нетерпеливых оценок, о которых типичный конечный пользователь, вероятно, не будет знать или беспокоиться . Единственные действительно видимые изменения - это, по сути, просто изменения правописания (обесценивание .' и присвоение permutedims второго аргумента по умолчанию).

Таким образом, консенсус сообщества, похоже, изменился почти на 180 градусов за очень короткое время (примерно во время публикации @Sacha0 https://github.com/JuliaLang/julia/issues/20978#issuecomment-347360279). Был ли пост Саши настолько красноречивым, что просто всех изменил? (Это нормально, если это так, я просто хочу понять, почему мы, кажется, движемся вперед по пути, который всего несколько дней назад мы все, казалось, считали неправильным.)

Я забыл, предлагал ли кто-нибудь это, но могли бы мы просто сделать transpose(::AbstractMatrix{AbstractMatrix}) (и, возможно, transpose(::AbstractMatrix{AbstractVector}) ) рекурсивным, а transpose нерекурсивным в противном случае? Похоже, что он охватывает все основы, и я не могу придумать другого варианта использования, в котором вы бы хотели, чтобы tranpose был рекурсивным.

Таким образом, консенсус сообщества, похоже, изменился почти на 180 градусов за очень короткое время (примерно во время публикации @ Sacha0 сообщения №20978 (комментарий)). Был ли пост Саши настолько красноречивым, что просто всех изменил? (Это нормально, если это так, я просто хочу понять, почему мы, кажется, движемся вперед по пути, который всего несколько дней назад мы все, казалось, считали неправильным.)

Если бы я только был таким красноречивым 😄. Вы видите, что консенсус на самом деле не сформировался. Скорее, (1) участники, которые поддерживают статус-кво, но отказались от обсуждения из-за истощения, вернулись, чтобы выразить свое мнение; и (2) другие стороны, которые не учли, что повлечет за собой отход от статус-кво на практике (и как это может сыграть с соображениями освобождения), сформировали более сильное мнение в пользу статус-кво и выразили это мнение.

Учтите, что это обсуждение в той или иной форме ведется на github с 2014 года и, вероятно, раньше в автономном режиме. Для постоянных участников такие обсуждения становятся утомительными и цикличными. Есть значимая работа, которую нужно сделать, кроме участия в этом обсуждении - например, написание кода, что доставляет больше удовольствия - в результате истощение среди этих долгосрочных участников. Следовательно, в тот или иной период разговор оказывается однобоким. Лично я примерно нахожусь на этом пороге отсева, поэтому сейчас я собираюсь сосредоточиться на написании кода, а не продолжать обсуждение. Всем спасибо и всего наилучшего! :)

Я немного проголосую за нерекурсивное транспонирование и ctranspose для AbstractArrays, причем оба они рекурсивны для AbstractArray {T}, где T <: AbstractArray.

Я согласен с тем, что в некоторых случаях рекурсивное поведение является «правильным», и я вижу вопрос в том, как добиться правильного поведения с наименьшим удивлением для тех, кто использует и разрабатывает пакеты.
В этом предложении рекурсивное транспонирование для настраиваемых типов является отказом: вы соглашаетесь, делая свой тип AbstractArray или определяя соответствующий метод.
Base.transpose(AbstractArray{MyType}) или Base.transpose(AbstractArray{T}) where T<: MyAbstractType .
Я думаю, что стратегия утиной печати для рекурсивных транспонирований (просто рекурсивное выполнение без запроса) вызывает несколько сюрпризов, как описано выше. Если вы введете отдельные ctranspose и adjoint или более сложные предложения, такие как concadjoint и flip, пользователи столкнутся с ними и попытаются их использовать, а сопровождающие пакетов постараются поддержать их все.

В качестве примера того, что было бы трудно поддерживать в рамках новых предложений: массивы normal, transpose, ctranspose и конъюнктуры должны иметь возможность иметь представления (или ленивую оценку), взаимодействующие с представлениями ReshapedArray и SubArray. (Я не знаю, производят ли они представления по умолчанию или только при использовании @view .) Это связано с работой над понижением A*_mul_B* и вызовами BLAS нижнего уровня с флагами 'N', «T» и «C» для плотных массивов, как было отмечено в другом месте. Было бы легче рассуждать, если бы они лечили normal , transpose , ctranspose и conj
на равных. Обратите внимание, что сам BLAS поддерживает только «N» для нормального, «T» для транспонирования и «C» для ctranspose и не имеет флага для конъюнктуры, что я считаю ошибкой.

Наконец, для согласованности с массивами и преобразованиями более высоких измерений, я считаю, что подходящее обобщение транспонирования и ctranspose состоит в том, чтобы перевернуть все измерения, т.е.
транспонировать (A :: Array {T, 3}) = permutedims (A, (3, 2, 1)).

Ура!

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

При этом транспонирование матриц и присоединение / ctranspose никогда не обсуждалось, особенно его рекурсивный аспект, который был почти беззвучно представлен в https://github.com/JuliaLang/julia/pull/7244 с единственными матрицами блока мотивации. . Были приведены различные причины и мотивы для рекурсивного присоединения (после фактов), и большинство людей могут согласиться с тем, что это хороший (но не единственный) выбор. Однако у транспонирования нет ни одной мотивации или фактического варианта использования.

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

  • Мы обсудили, стоит ли поддерживать блочные матрицы (и более экзотические структуры) в LinAlg . Возможны следующие варианты реализации: никакого рекурсивного материала (кроме + , * и conj потому что такова природа общих функций в Julia), все рекурсивное (статус-кво), или попытка какой-то проверки типа или признака того, должен ли элемент выполнять рекурсивную линейную алгебру или рассматриваться как скаляр.
  • Нам нужен удобный способ для пользователей переставлять размеры 2D-массива данных. У нас есть нерекурсивный transpose , flip , сокращенный синтаксис permutedims (этот PR был отправлен первым исключительно потому, что это наименьшее количество символов для реализации и, вероятно, имеет смысл даже если мы делаем что-то еще), своего рода проверка типа или признак того, должен ли элемент выполнять рекурсивное транспонирование (возможно, даже повторно вводя transpose(x::Any) = x ...).
  • Парсер Julia ведет себя странно, например, x' * y -> Ac_mul_B(x, y) что немного похоже на бородавку, которой в идеале не будет в версии 1.0. Это было невозможно до тех пор, пока мы не сможем поддерживать быстрый BLAS (без дополнительных копий) без него, таким образом, ленивое транспонирование и присоединение матриц.
  • Код в LinAlg довольно велик и накапливался за несколько лет. Многие вещи, такие как умножение матриц, можно было бы реорганизовать, чтобы сделать их более дружественными к признакам, возможно, с помощью системы диспетчеризации, более похожей на новую broadcast . Я думаю, что именно здесь мы можем упростить отправку нужных массивов (я думаю, PermuteDimsArray сопряженных измененных представлений полосовых матриц) в BLAS. Однако это не сделает версию 1.0, и мы также пытаемся избежать снижения производительности, не делая код намного хуже. Как указал Саша (и я обнаруживаю), транспонирование представлений с широким диапазоном поведения элементов (рекурсивное сопряжение, рекурсивное транспонирование, конъюгация, ничего) создает дополнительную сложность и кучу новых методов, чтобы все работало так, как они находятся.

Если мы думаем о версии 1.0 как о некоторой стабилизации языка, то в некотором смысле самым большим приоритетом для изменения поведения является третий. Я бы сказал: язык (включая синтаксический анализатор) должен быть наиболее стабильным, за ним следует Base , за которым следует stdlib (который может включать или не включать LinAlg , но я думаю, что почти наверняка будет включать BLAS , Sparse и т. Д. Однажды). Это изменение на самом деле не влияет на пользователей (в основном разработчиков библиотек), поэтому я не удивлюсь, если мнения людей здесь разойдутся.

Пятно на Энди! :)

Думаю, осталось только сделать adjoint и transpose ленивыми по умолчанию?

Можно ли это сейчас закрыть?

Далее: «Серьезное отношение к скалярному транспонированию»

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

нет

:)

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

Определенно кажется хорошим предметом для пакета.

хороший интерфейс для указания различных трехмерных транспозиций и тензорных умножений

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

Да, TensorOperations.jl выглядит неплохо. Я слегка пошутил, но получил то, что мне нужно.

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

Смежные вопросы

manor picture manor  ·  3Комментарии

arshpreetsingh picture arshpreetsingh  ·  3Комментарии

wilburtownsend picture wilburtownsend  ·  3Комментарии

i-apellaniz picture i-apellaniz  ·  3Комментарии

yurivish picture yurivish  ·  3Комментарии