当前, 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是递归的,则所有作品。 这只是一个例子,但可以概括。
推理似乎如下:
ctranspose
应该是递归的ctranspose == conj ∘ transpose == conj ∘ transpose
transpose
因此也应该是递归的我认为这里存在一些问题:
ctranspose == conj ∘ transpose == conj ∘ transpose
,尽管名称使这似乎不可避免。conj
在数组上逐元素操作的行为是Matlab的不幸保留,它并不是一个数学上合理的操作,就像exp
在元素上实际操作在数学上并不合理,而expm
确实是一个更好的定义。ctranspose
应该是递归的。 在没有共轭的情况下,没有充分的理由使转置成为递归。因此,我将提出以下更改以纠正这种情况:
ctranspose
(也称为'
)重命名ctranspose
adjoint
–这确实是该操作的作用,它使我们摆脱了必须等同于conj ∘ transpose
的暗示。 conj(A)
,而赞成conj.(A)
。adjoint
(née ctranspose
)上添加一个recur::Bool=true
关键字参数,以指示是否应递归调用自身。 默认情况下会。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
的非数字(或更具体地说,非实数,非-复杂值)。
[本期是ω₁部分系列的第二期。]
上一期的合乎逻辑的继承人...👍
conj在数组上按元素进行操作的行为是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://zh.wikipedia.org/wiki/Complexification#Complex_conjugation
(这种处理是相当正式的;但是,如果您在Google中搜索“复杂共轭矩阵”或“复杂共轭矢量”,则会发现无数用法。)
如果将复数向量映射到共轭向量( conj
现在做了什么)是不同于将其映射到共轭向量( '
做什么)的重要操作,那么我们当然可以保留conj
表示对向量(和矩阵,以及更高的数组)的操作。 这提供了conj
和adjoint
之间的区别,因为它们在标量上会一致,但在数组上的行为会有所不同。
在那种情况下, conj(A)
应该在每个元素上调用conj
还是调用adjoint
? 将复数表示为2x2矩阵示例将表明conj(A)
实际上应该在每个元素上调用adjoint
,而不是调用conj
。 这将使adjoint
, conj
和conj.
所有不同的操作:
adjoint
:交换i
和j
索引,然后将adjoint
递归映射到元素上。conj
:将adjoint
映射到元素上。conj.
:将conj
映射到元素上。conj(A)
应该在每个元素上调用conj
。 如果您用2x2矩阵表示复数,则将具有不同的复数向量空间。
例如,向量共轭的一种常见用法是在分析实矩阵的特征值时:特征值和特征向量以复共轭对的形式出现。 现在,假设您有一个由2d2实矩阵的2d数组A
表示的块矩阵,作用于由2分量矢量的1d数组表示的分块矢量v
。 对于具有特征向量v
λ
的A
任何特征值λ
,我们期望具有特征向量conj(v)
的第二个特征值conj(λ)
conj(v)
。 如果conj
adjoint
递归调用
(请注意,即使对于矩阵,其伴随关系也可能不同于共轭转置,因为以最通用的方式定义的线性算子的Matrix
,我们应该采用与给出的标准内部积对应的伴随dot(::Vector,::Vector)
。但是,某些AbstractMatrix
(或其他线性运算符)类型完全有可能要重写adjoint
来执行其他操作。)
相应地,为3d数组定义代数合理的adjoint(A)
也存在一些困难,因为我们没有将3d数组定义为线性运算符(例如,没有内置的array3d * array2d
操作) 。 也许adjoint
仅应在默认情况下为标量,1d和2d数组定义?
更新:哦,很好:我们现在也没有为3d数组定义ctranspose
。 继续。
(OT:我已经很期待“认真对待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/而不能m.(v)' == m.(v')
将成立,但是如果我对@stevengj的理解正确,那么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
是否具有数组方法或仅用于标量方法都是设计选择看起来还好吗? (Steven关于复数为2x2矩阵的示例的观点是,这是一个向量空间,其系数形式上/实际上是实数,因此共轭在这里无效。)adjoint
具有代数含义,这在许多重要领域中都是至关重要的,这些领域与线性代数密切相关(请参阅1和2和3 ,在物理领域也都有很大的应用)。 如果添加adjoint
,则它应允许所考虑的操作使用关键字参数-在向量空间/功能分析中,此操作通常是由内积引起的,因此关键字参数可以是形式。 为Julia的移调设计什么,应该避免与这些应用程序冲突,所以我认为adjoint
有点收费?@felixrehren ,我认为您不会使用关键字参数来指定引起伴随的内部乘积。 我想您将使用不同的类型,就像您要更改dot
的含义一样。
我的偏好会更简单一些:
conj
不变(基本)。a'
对应于adjoint(a)
,并使其始终递归(因此,对于未为元素定义adjoint
的字符串数组等会失败)。a.'
对应于transpose(a)
,并使其从不递归(只是permutedims
),因此忽略元素的类型。我真的没有看到非递归adjoint
。 作为扩展,我可以想象一下递归transpose
,但是在任何必须将a.'
更改transpose(a, recur=true)
,看起来都一样容易调用另一个函数transposerecursive(a)
。 我们可以在Base中定义transposerecursive
,但我认为对此的需求非常少,以至于我们应该等待它是否真正出现。
我个人认为,对于用户(以及实现方式)而言,保持这一简单性将是最简单的,并且在线性代数方面仍然是可以辩护的。
到目前为止(大多数)线性代数例程非常牢固地植根于标准数组结构和标准内积。 在矩阵或向量的每个插槽中,放置“字段”的一个元素。 我会争辩说,对于字段元素,我们关心的是+
, *
等,以及conj
,而不是transpose
。 如果您的字段是一个特殊的复杂表示形式,看起来有点像2x2实数矩阵,但是在conj
下变化,那么就可以了-它是conj
的属性。 如果只是2x2的真实AbstractMatrix
在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
来获得此元素类型而又没有运行时惩罚或通过调用推断来计算输出类型的方法。 从语言的角度来看,我猜测当前的行为(调用推理)是不可取的。
注意:也没有什么可以阻止用户定义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
和非共轭“点积”之间定义相似的关系,这将证明递归转置。 但是,用于复杂数据的非共轭“点积”根本不是真正的内积,它们的显示频率几乎不如真正的内积-当操作员以复杂的形式编写时,它们是由双正交关系产生的,对称(不是Hermitian)形式-甚至没有内置的Julia函数或线性代数中的通用术语,而想要交换任意非数字数组的行和列似乎更为常见,尤其是在广播中。 这就是为什么我可以支持将transpose
设为非递归,而将adjoint
设为递归。
我懂了。 因此,将'
重命名adjoint
将是更改的一部分,以表明不是conj ∘ transpose
?
总是让我困惑的是递归数组:在这种情况下,标量是什么? 在我所熟悉的所有情况下,都说向量的元素是标量。 即使在数学家在纸上写块向量/矩阵结构的情况下,我们仍然知道这只是较大的向量/矩阵的简写,其中元素可能是实数或复数(即BlockArray
)。 您期望标量能够在*
相乘,并且向量和对偶之间的标量类型通常没有区别。
@andyferris ,对于一般的向量空间,标量是一个环(数字),您可以将向量乘以。 我可以做3 * [[1,2], [3,4]]
,但是我不能做[3,3] * [[1,2], [3,4]]
。 因此,即使对于Array{Array{Number}}
,正确的标量类型也是Number
。
同意-但是通常说这些元素是(相同)标量,不是吗?
无论如何,我所见过的治疗方法都是从戒指开始,然后在其中建立向量空间。 戒指支持+
, *
,但是我还没有看到一种要求它支持adjoint
, dot
或其他价格的处理方法。
(对不起,我只是想了解我们正在建模的基础数学对象)。
它取决于您所说的“简写”和“要素”的含义。
例如,函数的有限大小向量和无限维算符的“矩阵”是很常见的。 例如,考虑宏观麦克斯韦方程的以下形式:
在这种情况下,我们有一个2x2的线性算子矩阵(例如卷曲),作用于2个分量的矢量,其“元素”是3个分量的矢量场。 这些是复数的标量域上的向量。 从某种意义上讲,如果深入挖掘,向量的“元素”就是复数-空间中各个点处的场的各个分量-但这很模糊。
或者,也许更准确地说,我们是否不能总是使用圆环在某种基础上描述向量的系数(考虑到Julia的事物的阵列性质,我们并非没有基础)?
无论如何,结果仍然是伴随必须是递归的,不是吗? 我不明白你在说什么。
(我认为我们绝对可以毫无基础,因为数组的对象或元素可以是符号表达式ala SymPy或代表抽象数学对象的某些其他数据结构,并且伴随元素可以返回另一个对象,当相乘时可以计算出一个例如积分)。
我只是想更好地理解,而不是提出特定要点:)
当然,在上面,伴随是递归的。 我想知道例如在上面是否最好将[E, H]
视为BlockVector
( BlockVector
其(无限大?)许多元素是复杂的,或视为2向量,其元素为矢量场。 我认为在这种情况下, BlockVector
方法不可行。
总之感谢。
因此,也许我可以这样表达自己的想法:
对于矢量v
,我有点希望length(v)
描述矢量空间的维数,即我需要(完全)描述矢量空间元素的标量系数的数量,并且同样, v[i]
返回标量系数i
。 到目前为止,我还没有将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
和BlockMatrix
(以及BlockDiagonal
等)的技术,它们可以对完全展开的数组进行高效,扁平化的显示,因此“这种方法至少应该可行。 我也正好想这将是一样用户友好的递归方法(一些额外的字符键入BlockMatrix([A B; C D])
换来的是什么可能是一个更好的语义,而且可能更可读的代码-我敢肯定,这些积分尚待辩论)。 但是,要实现所有这些将需要更多的工作。
@andyferris你说的对,它们不是圆环,因为未定义*
,但是我想我们在同一页面上可以轻松地定义*
,并且有很多不同的方法可以实现环形结构。 所以我想我们在同一页面上:输入是解决此问题的更可扩展的方法。
!(关于标量:标量不一定完全扁平结构的元件的原因是这方面的一个1维的复数向量空间是一维以上的复数和2维以上的实数;表示既不是四元数在复数上是二维的,八元数在四分位数上是二维的,复数上是4维的,实数上是8维的,但没有理由再要求八元数是“ 8维”的而不是坚持认为它们的标量不是实数;这些是不是逻辑强迫的选择,这纯粹是表示问题,应该让用户有这种自由,因为每种情况下线性代数都是相同的。具有[截断的]多项式环或数字字段的代数扩展的此类空间的更长链,它们在矩阵上都有活泼的应用。朱莉娅不必走得太远-但是我们应该记住它的存在,以免阻止它。 也许是话语话题? :))
@felixrehren ,最好将环R上的向量的向量理解为直接和空间,它也是R上的向量空间。将向量本身称为“基础环”是没有意义的,因为通常它们不是一个“环”。环。 该推理完全适用于[[1,2], [3,4]]
。
共轭和伴随通常是独立的概念; 说“标量元素的共轭”应该在这里完成工作对我来说似乎是完全不正确的,与“严肃的”相反,除了下面提到的特殊情况。
考虑在某些环上的某些希尔伯特空间H中的两个元素|u⟩和|v⟩的“ 2-分量列向量”(|u⟩,|v⟩)的情况(用复数rac表示),用狄拉克表示法: “向量的向量。” 这是具有自然内积⟨(|u⟩,|v⟩),(|w⟩,|z⟩)⟩=⟨u|w⟩+⟨v|z⟩的直接和希尔伯特空间H⊕H。 因此,伴随物应产生由“行向量”(⟨u|⟨v|)组成的线性算子,其元素本身就是线性算子:伴随物为“递归” 。 这完全不同于复合共轭物,后者由于下环的共轭作用而产生具有相同希尔伯特空间H⊕H的元素。
通过引用四元数等上的向量,使您感到困惑。 如果向量的“元素”也是下面的“复杂”环的元素,那么这些元素的伴随和共轭当然是同一回事。 但是,这并不适用于所有直接乘积空间。
换句话说,对象类型需要指示两个不同的信息:
对于Vector{T<:Number}
,我们应将其理解为表示标量环为T
(或对于复杂的向量空间为complex(T)
),并且内积是通常的欧几里得。
如果您有向量的向量,则它是希尔伯特空间的直接和,并且环是各个向量的标量环的提升,点积是元素的点积之和。 (如果不能推广标量或不能对点积求和,那么它根本不是向量/希尔伯特空间。)
如果您有标量T<:Number
,那么conj(x::T) == adjoint(x::T)
。
因此,如果您尝试用2x2矩阵m(z)::Array{T,2}
表示复数z::Complex{T}
,则与z
相比,您的类型指示的是不同的环T
和不同的内积z
,因此您不应期望conj
或adjoint
给出相同的结果。
我有点-您在说@felixrehren。 如果标量是实数,则可以将八进制数视为8维向量空间。 但是,如果标量系数是一个尾数,则存在表示所有尾数的平凡的1维基础。 我不确定这是否与我所说的(或我的意思是:smile :)不同,如果我们有一个AbstractVector{T1}
它可能与一个不同维度的AbstractVector{T2}
同构( length
)。 但是T1
和T2
都应该在Julia运算符+
和*
下都是环(同构可能保留+
的行为但不是*
或内部乘积或伴随)。
@stevengj我一直认为直和酷似我的BlockVector
。 您有两个(不相交)不完整的基数。 在每个基础内,您可能可以在第一个基础上形成c₁|X₁⟩+ c 2 | X2⟩或在第二个基础上形成c₃|Y₁⟩+c₄| Y2⟩叠加。 “直接和”代表看起来像(c₁|X₁⟩+c₂|X⟩l)+(c₃|Y₁⟩+c₄|Y⟩l)的状态空间。 在我看来,将其与复数(即c₁|X₁⟩+ c 2 | X2⟩+c₃|Y₁⟩+c₄| Y2⟩之类的状态)的维数基数分开的唯一特殊之处是括号-对我来说,这似乎是一种记号; 直接和只是将其写在纸上或在计算机上以阵列的一种便捷方式(可能对例如使我们进行分类并利用对称性或拆分问题(可能使其并行化)等有用) )。 在此示例中,X⊕Y仍然是标量复杂的四维向量空间。
这就是让我想要eltype(v) == Complex{...}
和length(v) == 4
而不是eltype(v) == Vector{Complex{...}}
和length(v) == 2
。 它可以帮助我了解和推理底层的环和向量空间。 难道不是这个“扁平化”空间,您可以在其中进行“全局”换位和逐元素conj
来计算伴随数吗?
(当我只写一个帖子时,您收到了两个帖子, @ stevengj :smile :)
当然,如果我们更喜欢使用嵌套数组作为直接和的约定(就像我们现在所做的那样),而不是使用BlockArray
,那么这也可能是很好且一致的! 我们可能会受益于一些额外的函数来确定标量字段(一种递归eltype)以及有效的平坦维数可能是多少(一种递归大小),从而使我们容易推断出我们正在做的线性代数。
明确地说,我对这两种方法都感到满意,并且从这次讨论中学到了很多东西。 谢谢。
@andyferris ,仅仅因为两个空间是同构的并不意味着它们是“相同”的空间,并且争论其中哪个是“真的”矢量空间(或者它们是否“真的”相同)是形而上的泥潭从那里我们将永远无法逃脱。 (但是,无穷维向量空间的直接和的情况说明了直接和的“扁平化”概念的局限性是“一个元素的所有元素之后是另一个元素的所有元素”。)
同样,我不确定您的意思是什么。 您是否认真地建议Julia中的eltype(::Vector{Vector{Complex}})
应该是Complex
,或者length
应该返回长度的总和? 是否应该强迫Julia中的每个人都将您的“展平”同构用于直接和空间? 如果不是,则伴随必须是递归的。
而且,如果您“只是试图更好地理解而不是提出要点”,您可以将其带到另一个论坛吗? 在没有关于直接和空间含义的形而上学的争论的情况下,这个问题就令人困惑。
仅仅因为两个空间是同构的并不意味着它们是“相同”的空间
我绝对没有建议。
同样,我不确定您的意思是
我严重建议您如果要使用AbstractArray
进行线性代数运算,则最好将eltype
做为环(支持+
, *
和conj
)排除了Vector
Eltype,因为[1,2] * [3,4]
不起作用。 向量的length
实际上代表线性代数的维数。 是的,这排除了无限维的向量空间,但是通常在Julia中AbstractVector
不能大小无穷大。 我觉得这将使人们更容易推理出如何在Julia上实现和使用线性代数功能。 但是,用户必须通过BlockArray
或类似方式显式表示直接和,而不要使用嵌套数组结构。
这是一个令人不安的建议,确实存在明显的缺点(您已经提到了一些缺点),因此,如果我们说“这是一个好主意,但不切实际”,我不会感到不满。 但是,我也一直试图指出,嵌套数组方法使底层线性代数的某些事情变得更加不透明。
所有这些似乎与OP直接相关。 使用强制展平的方法,我们将放弃递归转置/伴随,如果我们放弃了递归的转置/伴随,则只有展平的结构才可以用于线性代数。 如果我们不执行扁平化方法,则必须保持递归伴随。 对我来说,这似乎是一个相关的决定。
您是否认真地建议Julia中的
eltype(::Vector{Vector{Complex}})
应该是Complex
,或者length
应该返回长度的总和?
不,对不起,也许我写的内容不清楚-我只是建议在某些情况下可以方便使用两个新的便利功能。 例如,有时您可能想要该戒指的zero
或one
。
您是说茱莉亚的每个人都应被迫对直和空间采用“展平”同构吗?
我建议,是的,是的。 我不是100%相信这是最好的主意,但是我觉得有一些优点(和缺点)。
在这里返回可行的更改的领域,似乎@stevengj是我的建议的简化版本,即:
- 保持
conj
不变(基本)。- 使
a'
对应于adjoint(a)
,并使其始终递归(因此,对于未为元素定义伴随的字符串数组等会失败)。- 使
a.'
对应于transpose(a)
,并使其从不递归(只是permutedims
),因此忽略元素的类型。
可以从中提取的主要实际“待办事项”是:
ctranspose
重命名adjoint
。transpose
更改为非递归。我怀疑依赖递归转置的情况很少,我们可以在1.0中进行更改并将其列为NEWS中的中断。 将ctranspose
更改adjoint
可以进行正常的弃用操作(但是我们将其更改为1.0)。 还有什么需要做的吗?
@StefanKarpinski ,我们还需要对RowVector
进行相应的更改。 一种可能性是将RowVector
划分为惰性非递归transpose
和adjoint
Transpose
和Adjoint
类型,并摆脱Conj
(懒惰的conj
)。
其他一些至少与切线相关的更改
transpose
和adjoint
的AbstractMatrix
adjoint(::Diagonal)
递归(可以说,我们应该在Julia v0.6.0之前的transpose
和ctranspose
修复Diagonal
“错误”)v = Vector{Vector{Float64}}(); dot(v,v)
(当前存在错误-尝试返回zero(Vector{Float64})
)OK,我一直在努力采取递归块矩阵更严重,而支持这一行动,我也试图改善的行为Diagonal
这里有(已经有一些方法,其预料到块对角结构,例如getindex
,因此在这种情况下使转置递归看起来很自然。
让我受阻的是,如何直接激励上面所有讨论的话题,是如何在这种结构上进行线性代数运算,包括inv
, det
, expm
, eig
,依此类推。 例如,有一种det(::Diagonal{T}) where T
的方法,该方法仅获取所有对角元素的乘积。 对于T <: Number
出色地工作,并且如果所有元素都是相同大小的方阵也递归转置(编辑:伴随)是正确的事)。
但是,如果我们想到一般的块对角矩阵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引入了(相当不错的)点呼叫广播语法,这就变得有点不幸了。
我们可以将自己的路径与MATLAB分开,并对此进行更改-但我认为对此存在单独的讨论(有人记得吗?)。
啊,那很不幸。 谢谢!
另一个待办事项:
issymmetric
和ishermitian
以匹配transpose
和adjoint
–每对中的前者是非递归的,每对中的后者是递归的。在我们新的.
语法中,Matlab .'
语法绝对是不幸的。 我不会反对更改此设置,但是随后我们需要一种新的转置语法,并且不确定是否有可用的语法。 有人对换位有什么建议吗?
让我们将转置语法讨论移至此处: https :
对于transpose
/ ctranspose
/ adjoint
是递归的,我没有强烈的意见,但我宁愿不要将A::Matrix{Matrix{T}}
像块矩阵一样懒惰的hvcat
感觉, 一点。 也就是说,如果A
的元素都是相同大小的正方形矩阵(即形成环),则我希望det(A)
再次返回该大小的正方形。 如果它们是矩形或不同大小,我会期望出现错误。
也就是说,块矩阵/懒猫类型可能有用,但是应该一直使用,并且还应定义例如getindex
来处理扁平化数据。 但是我绝对不希望这个概念被Matrix
或任何其他现有的矩阵类型(如Diagonal
吸收。
@martinholters,这令人放松。 我在该线程的前面感到恐慌的想法是,我们应该能够以某种方式将Julia的整个线性代数框架应用于Matrix{Matrix}
的任意块矩阵(子矩阵具有不同的大小)。
我为“拼合”而争论的并不是对元素的含义进行任何花哨的内省,而只是将元素本身的优点视为环的元素。 但是,递归ctranspose
/ adjoint
将其扩展为允许像线性运算符一样起作用的元素,这似乎是正确且有用的。 (另一种情况是向量的元素,其作用类似于向量,其中递归伴随也是正确的)。
再说一遍-删除transpose(x) = x
和ctranpsose(x) = conj(x)
默认无操作行为的最初动机是什么? 这些对我来说似乎总是很有用。
再说一遍-删除transpose(x)= x和ctranpsose(x)= conj(x)的默认无操作行为的最初动机是什么? 这些对我来说似乎总是很有用。
这是由于未能专门化ctranspose
的自定义线性运算符类型(不能是AbstractArray的子类型)引起的。 这意味着他们从后备继承了错误的无操作行为。 我们试图构造分派,以使回退永远不会在无声的情况下是错误的(但它们可能具有悲观的复杂性)。 https://github.com/JuliaLang/julia/issues/13171
我们似乎有两个选择–在这两个选择中, transpose
变为非递归的,否则:
ctranspose
。ctranspose
。@stevengj , @jiahao , @andreasnoack –您在这里的偏好是什么? 其他?
自@jiahao的JuliaCon 2017演讲以来,我一直在思考这个问题。
对我来说,我仍然觉得应该将线性代数定义为元素类型T
的“标量”字段。 如果T
是一个字段(支持+
, *
和conj
(还包括-
, /
,.. 。)),那么我不明白为什么Base.LinAlg
会失败。
OTOH例如,谈论采用块矩阵的转置是非常普遍的(也是有效的)。 我认为我们可以在这里从“数学”类型理论中学习,例如,它试图通过讨论具有“一阶”标量集,“第二阶”标量集,订单”,“标量”,“标量”等。 我认为在处理数组数组时,我们有同样的问题(也有机会)-我们可以潜在地使用Julia的类型系统描述“真实”标量的“一阶”数组,标量数组的“第二阶”数组等我认为@stevengj ,我本人和其他人之间的漫长讨论是
对我而言,这里的关键点在于,要使“任意阶数”数组起作用,您必须教给“标量”一些通常只在向量和矩阵上定义的操作。 当前执行此操作的Julia方法是transpose(x::Number) = x
。 但是,如果我们通过删除此方法扩展数组和标量之间的语义区别,我将更喜欢-我认为这可能是完全可行的。
下一步将教Base.LinAlg
一阶矩阵和二阶矩阵之间的差等等。 我想到了两个可行的选择,可能还有更多选择,我真的不知道:
T
是AbstractMatOrVec
时, transpose(::AbstractMatrix{T})
是递归的,而当T
是Number
,并使其以某种方式可配置(选择启用,选择退出,无论如何)。 (在某种程度上,这过去和现在都是通过控制元素的transpose
方法的行为来完成的,但是我认为,控制外层的transpose
方法的行为更为干净。 )数组)。Array
在内的大多数AbstractArray
都是一阶的(它们的元素是标量字段),并使用不同的AbstractArray
类型(例如NestedArray
)包装数组并“标记”其元素将被视为数组而非标量。 我尝试了一下,但似乎上述选项对用户来说负担不大。我感觉到Julia在数组和标量之间建立了很强的语义分离,并且(在我看来)当前情况与此有点矛盾,因为标量必须“教”数组属性transpose
。 用户可以清楚地知道Matrix{Float64}
是标量矩阵(一阶数组),而Matrix{Matrix{Float64}}
是块矩阵(二阶数组)。 对于我们而言,使用类型系统或特征来确定数组是“一阶”还是其他类型可能更为一致。 我还认为,我列出的第一个选项将使Julia更加用户友好(因此我可以执行["abc", "def"].'
),但仍保留了使用块矩阵执行某些明智/有用的默认操作的灵活性。
抱歉这么长时间一直在竖琴,但是在JuliaCon与@jiahao交谈之后,我觉得我们可以在这里做得更好。 我确定我们可以使@StefanKarpinski在“工作”上面提到的其他选项,但是对我来说,这就像在引入RowVector
之前矩阵/矢量代数是“工作”一样,是“工作的”。
TLDR是-不要使( 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
变得很复杂,最好是使用专用类型àhttps: //github.com/KristofferC/BlockArrays.jl这个。
看起来不错,@ KristofferC。
@stevengj在上面提出了一个非常有效的观点,那就是有时您的元素在数学上可能是矢量或线性运算符,但不是AbstractArray
s。 我们绝对需要一种针对这些方法进行递归(c)转置的方法-不确定您是否考虑过此问题,但我想我已经提到了。
关于此主题的闲聊/#linalg对话的重点。 回顾上面的一些线程。 专注于语义,避免拼写。 (感谢所有参与该对话的人!:))
存在三种语义上不同的操作:
1)“数学伴随”(递归和惰性)
2)“数学转置”(理想的递归和惰性)
3)“结构转置”(理想情况下是非递归且“渴望”的)
现状: “数学伴”映射到ctranspose
, “数学转”到transpose
,和“结构转”到permutedims(C, (2, 1))
二维C
一维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
,这妨碍了通用编程。 不允许将结构转置应用到“数学”类型。
与(2b)和(2c)相比,提案(1)和(2a)得到了更广泛的支持。
请在#19344,#21037,#13171和Slack /#linalg中找到更多讨论。 最好!
谢谢你写的好!
我想在表上提出另一种可能性,该可能性与选项1配合得很好:数学矩阵和表格数据具有不同的容器类型。 然后A'
的含义可以由A
的类型决定(注意:不是如上所述的元素类型)。 这里的缺点是这可能在两者之间需要很多convert
(会吗?),当然,这会造成很大的破坏。 诚然,我非常怀疑这些好处是否可以证明这种破坏是合理的,但我仍然想提一提。
谢谢,出色的写作。 我投票赞成(1)。
伟大的写作。 请注意,对于(1),拼写可能是:
a'
(递归,惰性)conj(a')
(递归,惰性)a.'
(非递归,急切)因此,不必引入任何新的运算符-数学转置只是伴随和共轭的(懒惰,因此有效)组合。 最大的问题是,它使两个看起来相似的运算符,即后缀'
和.'
语义上截然不同。 但是,我认为在大多数正确的通用代码中,无论有人使用'
还是.'
都是99%的准确指示,表明他们的意思是“给我这件东西的伴随物”还是“交换”这东西的尺寸”。 此外,在有人使用'
且实际上意为“交换事物的维数”的情况下,其代码对于标量伴随不平凡的元素(例如复数)的任何矩阵都是不正确的。 在剩下的少数情况下,实际上有人说“给我这个伴随词的共轭”,我认为写conj(a')
会使含义更清晰,因为实际上人们实际上使用a.'
表示“交换a
的尺寸”。
确定我们为此所需的所有基础泛型类型仍有待确定,但是@andyferris和@andreasnoack已经对此事有所想法,而且似乎是可能的。
我认为我也应该更新,因为此讨论在其他地方进行。 我承认我完全错过了此提案的一件事(显而易见?),这是我假设我们将继续对线性代数使用.'
,但是我应该意识到情况并非如此!
例如, vector.'
不再需要是RowVector
(因为RowVector
是线性代数概念,我们首次尝试使用“对偶”向量)-它可以简单地成为Matrix
。 当我想要线性代数中的“非共轭转置”时,我真正在做的是采用conj(adjoint(a))
,这就是我们将要使用的并推荐所有线性代数用户使用(到目前为止,自从MATLAB有一个长期的“坏”习惯以来,MATLAB只使用a.'
而不是a'
来转置我知道是真实的任何矩阵(或向量),而我真正想要的是adjoint
(或双重)-名称的更改将对您大有帮助)。
我还将简要指出,这留下了一个有趣的空间。 以前vector'
和vector.'
必须满足双重要求,即进行“数据转置”并采用双重矢量。 现在.'
是用于操纵数组大小的,而'
是线性代数的概念,我们可以将RowVector
更改为一维DualVector
或其他东西完全。 (也许我们不应该在这里讨论这个问题-如果有人对此有胃口,让我们另作一个问题。)
最后,我将从Slack复制一份建议的行动计划:
1)将transpose
从LinAlg
移到Base
并添加关于它不再产生RowVector
或递归的depwarns(仅0.7)(如果可能)
2)到处都将ctranspose
重命名adjoint
3)确保Vector
, ConjVector
和RowVector
可以与'
和conj
和*
重命名RowVector
。 (在这里,我们还使conj(vector)
变得懒惰)。
4)引入一个惰性矩阵伴随物,该伴随物还与conj
和ConjMatrix
5)删除A_mul_Bc
等。将A_mul_B!
重命名mul!
(或*!
)。
6)利润
@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')
。 这些本身看起来很直观,并且issymmetric
AFAICT现有用法是针对Number
元素类型的(否则,经常会有其他错误/假设,对于嵌套数组而言,这些意义不大) 。
(另一种实现是issymmetric(a) == (a == conj(adjoint(a)))
...不能作为“漂亮”,也不能用于“数据”数组( String
等),但是在Number
数组上却是重合的
除非另有指示,否则我将实现以下行为:
issymmetric(a) == (a == a.')
和ishermitian(a) == (a == a')
似乎对我来说是正确的解决方案。
更新:改变了主意。 对称可能主要是线性代数
只是一个小建议:对两个向量的乘积( dotu
)求和通常很有用,而x.’y
返回标量是执行此操作的便捷方法。 所以我赞成不从transpose(::Vector)
返回Matrix
transpose(::Vector)
是的,它将是RowVector
因此您应该得到一个标量。 (请注意,转置不会递归)。
抱歉,可能会产生切线评论,但该线程中的许多人仍在谈论“向量空间的标量环”。 根据定义,向量空间的标量必须形成一个字段,而不仅仅是任何环。 与向量空间非常相似,但其标量仅形成环而不是字段的代数结构称为“模块”,但我认为模块太深奥了,无法在Base ... er中处理。 ..模块。
因为我们支持整数数组,所以我们有效地支持模块,而不仅仅是向量空间。 当然,我们也可以将整数数组视为行为嵌入到浮点数组中,因此它们是矢量空间的部分表示。 但是,我们也可以创建模块化整数的数组(例如),并且使用非素数模数,我们将使用不自然嵌入任何字段的环。 简而言之,我们实际上确实想考虑模块而不是向量空间,但是我认为我们的目的没有任何显着差异(我们通常只谈论+
和*
),因此对于我们而言,“向量空间”是“模块”的更常用的缩写。
是的,Julia当然确实(并且应该)支持通用模块以及真实向量空间上的代数运算。 但是,据我所知,社区的普遍哲学是Base
中的函数在设计时应考虑“'普通'通用线性代数例程”-例如,对整数矩阵进行精确的线性代数计算不属于Base
-因此,在做出基本设计决策时,我们应该仅假设标量构成一个字段。 (尽管正如您所说,实际上,这并不重要。)
交叉发布https://github.com/JuliaLang/julia/pull/23424#issuecomment -346678279
我非常感谢拉动请求所代表的将#5332和#20978向前推进的努力,并且参与了大多数相关计划。 我也很想了解拉取请求提出的术语选择和对下游的影响,因此定期尝试说服自己,这些选择会产生最佳的权衡。
每当我试图说服自己保持这种立场时,我都会因同样的疑虑而搁浅。 这些疑虑之一是此选择对
LinAlg
施加的巨大实现复杂性:合并转置和array-flip会强制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)
需要支持16种类型组合。这个问题随着参数的数量成倍增加。 例如,在没有合并的情况下,
mul!(C, A, B)
需要支持二十七种类型的组合,而在没有合并的情况下,mul!(C, A, B)
需要支持六十四种类型的战斗。 当然,将Vector
s和非Matrix
矩阵/运算符类型添加到混合中会使情况进一步复杂化。在进行此更改之前,这种副作用似乎值得考虑。 最好!
这篇文章巩固/回顾了关于github,slack和triage的最新讨论,并分析了前进的可能。 (https://github.com/JuliaLang/julia/issues/20978#issuecomment-315902532的精神继任者源于思考如何达到1.0。)
存在三个语义上不同的操作:
伴随称为adjoint
/ '
(但渴望与'
分开-涉及专门降低到A[c|t]_(mul|rdiv|ldiv)_B[c|t][!]
调用的表达式,这避免了中间渴望的伴随/转置) 。
转置称为transpose
/ .'
(与adjoint
)。
Array-flip对于二维C
称为permutedims(C, (2, 1))
,对于一维C
称为permutedims(C, (2, 1))
reshape(C, 1, length(C))
C
。
A[c|t]_(mul|rdiv|ldiv)_B[c|t]
以及相关的方法名称组合组合应该消失1.0。 删除特殊的降低/相关的方法名称需要懒惰的伴随和转置。ctranspose
)和转置以前通过通用的no-op后备广告如transpose(x::Any) = x
, ctranspose(x::Any) = conj(x)
和conj(x::Any) = x
与array-flip合并。 这些后备使得在某些用户定义的数字类型上涉及[c]transpose
操作默默失败(返回错误结果),而这些类型没有[c]transpose
专业化。 静默返回不正确的结果是个坏消息,因此这些后备功能已被删除(#17075)。 不引入更多的静默失败将是很好。.'
语法会很可爱。 必须删除上面提到的特殊降低措施以启用此更改。删除特殊的降低和相关的方法名称,避免引入静默故障,为转置和数组翻转提供直观方便的提示,并删除.'
。 以最小的附加复杂性和破坏性实现上述目标。
该领域已缩减为两个设计建议:
调用adjoint adjoint
,转置conjadjoint
(“共轭adjoint”)和array-flip transpose
。 adjoint
和conjadjoint
生活在LinAlg
,而transpose
生活在Base
。
调用伴随adjoint
,转置transpose
和array-flip flip
。 adjoint
和transpose
生活在LinAlg
,而flip
生活在Base
。
乍一看,这些建议似乎只是表面上的不同。 但是进一步的检查揭示了深层的实际差异。 避开这些命名方案的相对肤浅优点的讨论,让我们看一下那些深层的实际差异。
复杂:
建议一,通过调用array-flip transpose
,强制LinAlg
除了转置和伴随之外,还要处理array-flip。 因此, LinAlg
必须大大扩展通用操作支持的类型组合的集合; https://github.com/JuliaLang/julia/pull/23424#issuecomment -346678279通过示例说明了这种额外的复杂性,并且以下讨论暗含了这种额外复杂性的存在。
提案2仅需要LinAlg
支持转置和伴随,而LinAlg
现在需要。
破损:
提案1更改了现有操作的基本语义: transpose
变为数组翻转而不是转置,并且所有与transpose
相关的功能必须相应地更改。 (例如,与名称transpose
关联的LinAlg
中的所有乘法和左/右除运算都需要进行语义修订。)根据此更改的实现方式,此更改会导致无声破坏无论何时(有意或无意)依赖当前语义。
提案二保留了所有现有操作的基本语义。
耦合:
提案一将线性代数概念(转置)引入Base
,并将抽象数组概念(array-flip)引入LinAlg
,将Base
和LinAlg
强耦合
提案二将事物抽象数组和线性代数干净地分开,允许前者仅生活在基础中,而后者仅生活在LinAlg
,而没有新的耦合。
静音与响亮的失败:
当有人调用transpose
期望转置语义时(但是得到数组翻转语义),建议一导致了无声的错误结果。
提议2下的模拟正在非数字数组上调用transpose
,期望数组翻转语义。 在这种情况下, transpose
会抛出错误,有助用户指向flip
。
.'
:不弃用.'
的主要参数是transpose
的长度。 提案一用名称transpose
和conjadjoint
代替.'
,但这种情况并没有改善。 相反,提案二提供了名称flip
和transpose
,从而改善了这种情况。
在每个提案下,达到1.0会做什么? 在建议二下,通往1.0的路径更简单,所以让我们从那里开始。
在LinAlg
引入惰性伴随和转置包装器类型,例如Adjoint
和Transpose
。 介绍在这些包装类型上分派的mul[!]
/ ldiv[!]
/ rdiv[!]
方法,并包含相应的A[c|t]_{mul|ldiv|rdiv}_B[c|t][!]
方法的代码。 将后一种方法重新实现为前一种方法的简称。
这一步什么都不会中断,并立即启用.'
的特殊降低和弃用:
删除产生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)依靠特殊的降低来实现非Base
/ LinAlg
类型的A[c|t]_{mul|ldiv|rdiv}_B[c|t]
方法的代码将中断。 这样的代码将发出显式的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)
。 在弃用的情况下,可以在1.0中重新使用adjoint
/ transpose
(尽管周围有Adjoint(A)
/ Transpose(A)
,但我不确定是否会必要)。
最后...
在Base
引入flip
和/或Flip
Base
。 作为一项功能添加,如有必要,此更改可以在1.x中进行。
那提案一呢? 下面概述了两种可能的路径。 第一条路径合并了0.7中的更改,但涉及无声破坏。 第二条路径避免了无声破坏,但是涉及到更多的变化0.7-> 1.0。 两者都在不断发展代码库。 连续性较低的等效项可能会巩固/避免一些工作/流失,但可能会更具挑战性且容易出错。 正在进行的工作似乎遵循了第一条道路。
将transpose
的语义从转置更改为array-flip,还必须将所有与transpose
相关的功能的语义更改。 例如,所有A[c|t]_{mul|ldiv|rdiv}_B[c|t][!]
方法开始At_...
或结束..._Bt[!]
可能需要修改的语义。 还必须更改,例如Symmetric
/ issymmetric
的定义和行为。 将transpose
本身移动到Base
。
这些变化将无声地,广泛地打破。
在LinAlg
引入conjadjoint
LinAlg
。 此步骤需要恢复上一步中涉及的所有方法,但以其原始语义形式进行,并且现在使用与conjadjoint
关联的不同名称(例如, Aca_...
和..._Bca[!]
名称) 。 还需要为同时支持array-flip(现在transpose
),转置(现在conjadjoint
)和LinAlg
伴随的其他类型组合添加方法(例如ca
间变型A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!]
)。
在LinAlg
引入惰性懒惰并转置(称为conjadjoint
),比如说Adjoint
和ConjAdjoint
。 在Base
引入一个惰性数组翻转(称为transpose
)包装器类型,例如Transpose
。 介绍在这些包装类型上分派的mul[!]
/ ldiv[!]
/ rdiv[!]
方法,并包含相应的A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!]
方法的代码。 将后一种方法重新实现为前一种方法的简称。
删除产生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
来巩固/避免一些工作/流失,尽管这种方法可能会更具挑战性且容易出错。
在LinAlg
引入渴望的conjadjoint
LinAlg
。 将当前与transpose
关联的所有功能和方法(包括涉及t
A[c|t]_{mul|ldiv|rdiv}_B[c|t][!]
方法)迁移到conjadjoint
和派生名称。 将所有与transpose
相关的名称简称为新的等价conjadjoint
子代。 将所有与transpose
相关的名称弃用conjadjoint
等价的名称。
在LinAlg
引入惰性的伴随和转置(称为conjadjoint
)包装器类型,比如说Adjoint
和ConjAdjoint
。 介绍在这些包装类型上分派的mul[!]
/ ldiv[!]
/ rdiv[!]
方法,并包含相应的A[c|ca]_{mul|ldiv|rdiv}_B[c|ca][!]
方法的代码。 将后一种方法重新实现为前一种方法的简称。
在Base
引入一个懒惰的数组翻转包装器类型,称为Transpose
(有点令人困惑,因为同时使用转置语义将transpose
弃用为conjadjoint
)。 将array-flip( Transpose
)运行所需的所有常规方法添加到Base
。 然后将方法添加到LinAlg
中,以同时支持array-flip的所有其他类型组合(现在是Transpose
,但不支持transpose
),转置(现在是conjadjoint
和ConjAdjoint
),并要求与LinAlg
相连(例如,相当于A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!]
的mul[!]
/ rdiv[!]
/ ldiv[!]
A[c|t|ca]_{mul|ldiv|rdiv}_B[c|t|ca][!]
目前尚不存在)。
删除产生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
; 如果需要,可以在1.0中重新使用transpose
。 adjoint
可以无限期保留,也可以弃用0.7,而改为使用另一个拼写。 拼写conjadjoint
另一种方法可以直接输入0.7。
一个人可以在某种程度上合并步骤1和2,从而避免一些工作/流失,尽管这种方法可能会更具挑战性并且容易出错。
谢谢阅读!
谢谢你写的好。 我通常也同意提案2,另外一个原因是共轭伴随词根本不是标准术语,而且我个人觉得很混乱(因为有时在某些数学分支中,伴随词有时被用作简单转置的术语,而额外的形容词共轭词也是如此)似乎暗示发生了共轭)。
我尝试阅读上面详尽的讨论,但是看不到在什么时候决定/激励数学transpose
应该递归。 这似乎是通过一些私人讨论(在7月14日至18日之间的某个地方)进行的,此后突然引入了数学和结构转置。
@ Sacha0将其描述为
产生的方式:以前将“结构转置”与“数学伴随” /“数学转置”混为一谈
我同意(结构)转置与“伴随”混为一谈,但是这里的“数学转置”似乎从哪儿冒出来? 为了完整性/自包含性,可以简要地重申/总结该概念以及为什么要递归吗?
与此相关,我认为没有人真正使用抽象数学意义上的transpose
作为对线性映射的RowVector
s上,即它将RowVector
映射到RowVector
。 鉴于缺乏关于递归转置的论点,我确实看不到通常定义为(基本依赖)概念的纯矩阵转置与新提出的flip
操作之间没有区别。
感谢@ Sacha0的出色写作。 我赞成第2项提案(呼叫伴随adjoint
,转transpose
,和数组翻转flip
。 adjoint
和transpose
住在LinAlg
和flip
生活在Base
。)。 对我来说,这似乎可以提供最佳的最终结果(这应该是首要考虑的问题),并且还可以为v1.0提供一种更简洁的方法。 我同意您的上述观点,因此不再赘述,不过这里有一些补充意见。
支持非递归transpose
一种说法是.'
-syntax非常方便,因此可以用于Matrix{String}
。 假设语法消失了(#21037)并且必须使用transpose(A)
而不是A.'
,那么我认为flip
-函数将是一个受欢迎的补充(简称并且比transpose(A)
更清晰,并且绝对比permutedims(A, (2,1))
更好)。
提案2给出的LinAlg
和Base
之间的解耦也非常好。 不仅因为LinAlg
只需要关心如何处理Transpose
和Adjoint
,而且很清楚我们考虑了transpose
和adjoint
是LinAlg
操作,而flip
是仅翻转矩阵尺寸的通用AbstractMatrix
事物。
最后,我还没有看到关于transpose(::Matrix{String})
等问题的抱怨。 这真的是一个常见的用例吗? 在大多数情况下,从一开始就应该构造翻转矩阵。
此命名方案(建议2)可能确实更好。 伴随和转置是线性代数中的非常标准的术语,它的破坏力较小。
但是未能看到在什么时候决定/动机了数学转置应该递归
@Jutho与伴随词相同(复数的伴随词是共轭的)(因为复数是有效的秩一向量空间和线性运算符,适用内积和伴随概念),以及块矩阵的伴随是递归的...)。 例如,人们可能想对嵌套的真实矩阵进行线性代数运算。 我仍然看到很多代码使用.'
表示“真实矩阵或向量的伴随”,并且我也经常这样做。 在这里, '
不仅有效,而且更通用(适用于可能是复杂值和/或嵌套的数组)。 当您的方程式给您conj(adjoint(a))
,在嵌套的复杂矩阵上更真实地使用递归转置,这相对少见但仍会发生(如果没有与之相关的函数,我很高兴-上面及其他地方的讨论开始,人们开始称其为“数学转置”之类的不同事物,我选择了conjadoint
,但IMO并不是一个很好的术语,也许transpose
本身除外)。 在线性代数中,通常不会出现重新递归块矩阵的块但对块本身不做任何事情的非递归运算。
最后,我没有看到太多关于
transpose(::Matrix{String})
等问题的投诉。 这真的是一个常见的用例吗? 在大多数情况下,从一开始就应该构造翻转矩阵。
我认为这类事情对于Julia的广播业务而言是非常可取的。 例如,很常见的是要获取某些数据的外部(笛卡尔)乘积,并可能通过函数将其映射。 因此,我们可以使用broadcast(f, a, transpose(b))
或简单地f.(a, b.')
代替map(f, product(a, b))
f.(a, b.')
。 只需几个字符,它便具有强大的功能。
我的想法:为避免向此空间添加更多函数名(即flip
),我想知道是否可以在permutedims(a) = permutedims(a, (2,1))
使用排列的默认值。 不幸的是,它不如flip
短,但是比完整的permutedims(a, (2,1))
形式令人讨厌(并且具有教用户更多通用功能的副作用)。
( @ Sacha0非常感谢您在这里的Adjoint
和Transpose
包装器之间选择,或者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
(在数学意义上从未使用过,但在翻转矩阵索引意义上始终使用)都是递归的。 如果您自己定义一个新的标量字段类型,那么除了使用conj
之外,还需要为其定义adjoint
和transpose
只是一个奇怪的负担。在一个矩阵中。
那么,为什么要使用Julia中如此强大的类型系统,而不仅仅是拥有专用于块矩阵的类型。 因此,扮演恶魔的拥护者:
如前所述,我并不一定反对,只是质疑其有效性(即使在阅读了以上所有讨论之后),尤其是对于transpose
情况。
这可能会让我被禁止/开除
我知道这是个玩笑,但是拥有不受欢迎的意见绝对不会被任何人禁止。 只有长时间发生的不良行为才能导致禁止。 即使这样,禁令也不是
我只是不认为有严格的数学理由,因为矢量和运算符/矩阵不是递归定义的,它们是在标量字段上定义的(如果要使用模块而不是矢量空间,也可以通过扩展环定义) )。
这句话对我来说毫无意义。 向量的向量,函数的向量或矩阵的向量仍形成向量空间(在标量字段上),递归定义了+
和* scalar
,并且类似地,内部产品空间,其中内部产品是递归定义的。 向量只能是“标量列”的想法既违背了数学,物理学和工程学中的定义和通用用法。
使
a=[rand(2,2),rand(2,2)]
并观察a'*a
。 它不是标量。
在这种情况下, a
的“标量环”是2x2矩阵的环。 因此,这是一个语言问题,而不是伴随方式的问题。
在Julia中争论a'*a
应该如何工作(从当前的工作方式(或不工作))似乎很循环。
如果您自己定义新的标量字段类型,那么除了使用
conj
之外,还需要为其定义adjoint
和transpose
只是一个奇怪的负担在矩阵中
我认为这种负担比实际更具美学意义。 如果您可以将其设为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
,所以对于矩阵向量,由于我们没有为矩阵定义规范的内积,因此会出错。
我总是惊讶于转置是递归的..而且我无法想象任何人都不是。 (由于此讨论,我猜测Wikipedia文章对“线性映射转置”的观点至少增加了三倍。)
我也经常发现递归定义令人不安,并且普遍赞同@Jutho所表达的
我想我已经弄清楚了一直困扰着我的不一致之处-我们在这里继续使用环和字段(例如复数的2x2矩阵嵌入)作为示例,但实际上并没有解决,也没有激发递归转置(和伴随) )。
让我解释。 我大致同意的事情
LinAlg
概念,例如adjoint
,例如,我们应该定义adjoint(z::Complex) = conj(z)
。 (除了标量的向量和矩阵,还可以将LinAlg
概念扩展(由用户,我认为是)到其他对象- @stevengj提到了例如无限大小的向量空间(希尔伯特空间))。z = x + y*im
可以建模为Z = x*[1 0; 0 1] + y*[0 1; -1 0]
,而运算+
, -
, *
, /
和\
保留在这种同构中。 (不过请注意, conj(z)
变为adjoint(Z)
/ transpose(Z)
/ flip(Z)
-稍后再介绍)。adjoint
等)。Base.LinAlg
与1和2兼容似乎是合理的,但如果IMO 3自然适合,则仅应在Base
完成IMO 3(否则,我倾向于遵从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
可逆,请参见Wikipedia )|
考虑到像trace
和det
这样的操作返回标量,我认为很明显,我们用于LinAlg
Julia类型系统可能无法处理我们的Complex
2x2矩阵嵌入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
对于块矩阵和向量向量是方便的。 它不应该被用于类型“标” 2×2矩阵的数学正确性我标记动机Z
以上。 无论是否默认支持块矩阵,我认为这都是我们的选择,具有以下优点和缺点:
优点
+
, *
, conj
等下。如果有可能使其成为自然同构(与上面的Z
示例(需要CNumber
)不同,那为什么不呢?缺点
LinAlg
实现显着增加了复杂性(可能存在于另一个程序包中)。eig(block_matrix)
类的东西有点困难(但并非不可能)。 如果我们说LinAlg
支持eig
和LinAlg
支持块矩阵,那么我认为这是一个错误,直到它被修复。 鉴于LinAlg
提供了大量功能,很难看到我们会“完成”。transpose
类的操作的数据用户带来的不便,对我来说,问题是-我们是否选择说默认情况下LinAlg
期望AbstractArray
的元素是“标量”( 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)
吗?conj
都变得懒惰?flip
,是懒惰还是渴望?仅供参考,我尝试使用一种低摩擦的方法在#24839上“翻转”矩阵的尺寸,并使用一个较短的形式permutedims
。
我强烈支持@ Sacha0的建议2。我们尚未完全梳理递归与非递归伴随的理论基础,但与此无关的是:至少对我来说,它现在变得有用了,那么建议您在最后一刻改变一下。 如果需要的话,转置应该跟随伴随(= conj∘adjoint
)。
FWIW,Mathematica不执行递归Transpose
或ConjugateTranspose
:
我尝试阅读上面详尽的讨论,但是看不到在什么时候决定/有动机进行数学转置。 [...]为了完整性/自包含性,可以简要地重申/总结该概念以及为什么要递归吗?
我理解并赞赏这种观点,并希望在时间允许的范围内解决。 很遗憾,通俗易懂地解释为什么应根据定义来递归伴随和转置是充其量的挑战,而且很可能不可能简短地说。 上面所述的连贯,全面的文章需要花费大量时间来构建。 时间有限,在一天的过程中,我将尝试通过回应其中的特定要点/问题来解决前面几篇文章中的一些困惑; 当我这样做时,请忍受我:)。 最好!
解释为什么伴随...根据定义应该是递归的...
我很高兴为这次讨论做出贡献的每个人都同意adjoint(A)
应该是递归的。 唯一的争议是transpose(A)
是否也应该是递归的(以及是否应该为非递归转置引入新函数flip(A)
)。
我很高兴为该讨论做出贡献的每个人都同意adjoin(A)应该是递归的。
参见例如https://github.com/JuliaLang/julia/issues/20978#issuecomment-347777577和更早的注释:)。 最好!
递归伴随的参数是定义的基础。 dot(x,y)
应该是一个内部乘积,即产生一个标量,并且伴随的定义是dot(A'*x, y) == dot(x, A*y)
。 从这两个属性进行递归(对于dot
和adjoint
)。
(与点积的关系是伴随和转置是线性代数中重要运算的全部原因。这就是为什么我们为它们取一个名字,而不是为其他运算(例如将矩阵旋转90度(在Matlab中rot90
)。
请注意,使用flip
进行非递归转置的一个问题是Matlab和Numpy都使用flip
作为我们所谓的flipdim
。
我认为没有人真正使用抽象数学意义上的转置作为对线性映射的操作,原因很简单,矩阵的转置将是一个不作用于矢量而是作用于RowVectors的对象,即它将映射RowVector到RowVector。
但是,似乎没有递归转置并使其仅用于非数学应用似乎更简单,因为我认为即使在典型的矩阵方程中,它与线性映射的实际数学转置也几乎没有关系。
转置(在数学意义上从未使用过,但在翻转矩阵索引意义上始终使用)
这似乎有点回旋处,所以请耐心:)。
朱莉娅的adjoint
特别是指Hermitian伴随。 通常,对于具有各自对偶空间U *和V的U和V完整范数向量空间(Banach空间) ,对于线性图A:U-> V,则A的伴随物(通常表示为A )是线性图A *:V *-> U * 。 也就是说,伴随物通常是对偶空间之间的线性图,就像通常转置A ^ t是如上所述对偶空间之间的线性图一样。 那么,如何将这些定义与埃尔米特伴随的概念相协调呢? :)
答案在于一个人通常工作的空间所拥有的附加结构,即完整的内部乘积空间(希尔伯特空间)。 内积引起范数,因此完整的内积空间(Hilbert)是完整的范数空间(Banach),从而支持(Hermitian)伴随的概念。 这是关键:拥有内积而不只是范数,线性代数中最漂亮的定理之一就是里兹表示定理。 简而言之,Riesz表示定理指出,希尔伯特空间对它的对偶空间自然是同构的。 因此,在使用希尔伯特空间时,我们通常会识别空间及其对偶,并放弃区分。 进行这种识别是您如何得出熟悉的Hermitian伴随的概念,即A *:V-> U而不是A *:V *-> U * 。
当考虑希尔伯特空间时,通常会对转置进行相同的标识,使得A ^ t:V-> U也产生熟悉的转置概念。 因此要澄清一下,是的,转置的通用概念是应用于最熟悉的(希尔伯特)设置的转置通用概念,就像埃尔米特伴随的通用概念是应用于该设置的伴随的通用概念一样。 最好!
很抱歉打败一匹死马,但我想我会以另一种方式简要总结一下数学混乱的问题。 意见分歧的关键是,当T
不仅仅是Number
的子类型且没有类似数组的子结构时,如何数学地解释对象Vector{T}
。
一个流派接受@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)
的正确操作取决于表示的性质。 如果它是Wikipedia上描述的2x2表示形式,则conjugate
应该翻转矩阵。 但是由于上述原因, conjugate
绝对不应该总是翻转矩阵。 因此,这种方法实施起来有些混乱。
这是我自己的想法:对于将transpose
应用于元素进一步复杂的子结构的数组,是否应该递归没有“客观正确”的答案。 这取决于用户选择如何精确地在Julia中表示其抽象代数结构。 不过,支持完全任意数量的标量环似乎是非常困难的,因此,我认为出于实用性考虑,我们不应该如此雄心勃勃,而应该忘记模块对非标准环的深奥数学。 我们当然应该在Base
使用一个函数(语法比permutedims(A, (2,1))
更为简单),该函数与线性代数转换概念无关,并且仅翻转矩阵并且不进行任何递归操作,无论是否称为transpose
或flip
或其他。 这将是很好,如果adjoint
在和一个独立的移调功能(可能有不同的名称) LinAlg
是递归的,因为那样的话,他们可以处理块向量/矩阵和简单实现直接总和作为向量的向量,但这不是“客观数学正确性”所必需的,纯粹基于易于实现的理由做出该决定就可以了。
尽管我以前曾承诺不再发表评论,但不幸的是我不得不对此作出回应。
朱莉娅(Julia)的陪伴特别是指埃尔米特伴奏。 通常,对于具有各自对偶空间U *和V的U和V完整范数向量空间(Banach空间) ,对于线性图A:U-> V,则A的埃尔米特伴随(通常表示为A )是线性图A *:V *-> U *。 即,通常,埃尔米特伴随是双空间之间的线性图,正如通常转置A t是如上所述的双空间之间的线性图。 那么,如何将这些定义与埃尔米特伴随的概念相协调呢? :)
真的不对。 您在这里描述的实际上是转置,但是(正如我已经提到的),在某些字段中将其称为伴随(无Hermitian),并表示为A ^ t或A ^ *(从不A ^ dagger)。 实际上,它远远超出了向量空间,并且在范畴论中,这样的概念存在于任何单曲面范畴中(例如,n维定向流形的范畴Cob,以cobordisms为线性映射),在这里它被称为伴随伴侣(在实际上,可以存在两个不同的A和A ,因为左右对偶空间不一定相同)。 但是请注意,这永远不会涉及复杂的共轭。 V *的元素确实是线性图f:V-> Scalar,对于线性图A:U-> V和来自U的向量v,我们有f(Av)=(A ^ tf)(v) 。 由于f的作用不涉及复共轭,因此A ^ t的定义也没有。
答案在于您通常工作的空间所拥有的附加结构,即完整的内部乘积空间(希尔伯特空间)。 内积引发范数,因此完整的内积空间(Hilbert)是完整的范数空间(Banach),从而支持Hermitian伴随的概念。 这是关键:拥有内积而不只是范数,线性代数中最漂亮的定理之一就是里兹表示定理。 简而言之,Riesz表示定理指出,希尔伯特空间对它的对偶空间自然是同构的。 因此,在使用希尔伯特空间时,我们通常会识别空间及其对偶,并放弃区分。 进行这种识别就是您如何熟悉Hermitian伴随的概念,即A *:V-> U而不是A *:V *-> U *。
再次,我认为那是不完全正确的。 在内部积空间中,内部积是从conj(V)x V->标量(与conj(V)共轭向量空间)起的倍数形式dot
。 实际上,这允许建立从V到V *(或者从技术上从conj(V)到V *)的映射,这确实是Riesz表示定理。 但是,我们不需要引入Hermitian伴随。 确实,内积dot
本身就足够了,线性映射A
的Hermitian伴随关系使得
dot(w, Av) = dot(A' w, v)
。 这涉及复杂的缀合。
真的不对。 您在这里描述的实际上是转置,但是(正如我已经提到的),在某些字段中将其称为伴随(无Hermitian),并表示为A ^ t或A ^ *(从不A ^ dagger)。 [...]
@Jutho ,请参阅例如Hermitian伴随的
https://zh.wikipedia.org/wiki/Transpose_of_a_linear_map
特别是
https://zh.wikipedia.org/wiki/Transpose_of_a_linear_map#Relation_to_the_Hermitian_adjoint
@Jutho ,我看不到该页面部分与上面链接的页面上给出的定义之间的矛盾,也没有发现与上面发布的内容不一致。 最好!
我还将登录@ Sacha0的提案2。现在,我也可以接受带有1个参数的permutedims
。 我认为这比flip
。
@ Sacha0 ,那么我们有另一种解释方式。 我读为
对于给定的A:U-> V,
transpose(A)= dual(A)=(有时也)adjoint(A):V *-> U *
埃尔米特伴随(A)=匕首(A)=(通常是)伴随(A):V-> U
两者之间的关系是通过使用从空间到对偶空间的映射(即Riesz ...)精确获得的,这涉及复杂的共轭。 因此,埃尔米特伴生涉及共轭,转置不涉及共轭。
如果您还想调用第一个Hermitian伴随,那么您称之为转置? 您没有真正定义说明中的内容,您刚才提到
A的Hermitian伴随(通常表示为A )是线性映射A :V *-> U *。 也就是说,一般来说,埃尔米特伴随是对偶空间之间的线性映射,就像通常转置A ^ t是对偶空间之间的线性映射一样
那么,转置和Hermitian伴随在一起,那么有两种不同的方式将A:U-> V从V- > U转换为映射吗? 我真的很乐意对此进行进一步讨论,但我想我们最好在其他地方进行。 但实际上,请与我联系,因为我实际上非常有兴趣了解更多有关此的信息。
另请参阅http://staff.um.edu.mt/jmus1/banach.pdf ,以获得有关在Banach空间上下文中使用的伴随实际上是转置的,而不是Hermitian伴随(特别是线性而不是反线性的)的引用。转型)。 Wikipedia(和其他参考文献)确实将这两个概念结合在一起,使用希尔伯特空间中的Hermitian伴随的概念作为对Banach空间中伴随的广义定义的动机。 但是,后者实际上是转置的(不需要内部乘积,也不需要规范)。 但这就是我所说的换位,没有人真正在计算机代码中使用它。
对于朱莉娅·巴思(Julia Base):我不反对递归式埃尔米特式共轭; 我同意这通常是正确的做法。 我只是不确定当元素类型不是Number
时,Base是否应该尝试做聪明的事情。 即使T
是数字,Base也不支持非欧几里得内积的更常见用法(以及伴随的关联修饰定义),我也不认为应该存在。 因此,我认为主要动机是块矩阵,但我只是认为特殊目的类型(在Base或包中)更多是Julian,而且,正如@andyferris也提到的那样,它并不像LinAlg
的其余部分一样inv
(更不用说矩阵分解等)。
但是,如果递归的埃尔米特式共轭在这里保持不变(对我很好),那么我认为为了一致性, dot
和vecdot
应该对元素进行递归操作。 当前,情况并非如此: dot
在元素上调用x'y
(当元素是矩阵时不一样), vecdot
调用dot
在元素上。 因此,对于矩阵向量,实际上没有办法获得标量结果。 如果人们同意当前的实现与递归adjoint
并不一致,我很乐意编写PR。
至于transpose
,使其变得非递归并使那些不使用数值数据的人使用它似乎更为简单。 我认为大多数使用矩阵的人都知道术语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,本征等中的线性代数实现更为通用,这反映了Julia强大的类型/调度系统的可行性。
我认为选项2理想的总体原因再次是,朱莉娅的类型/调度系统使我们可以有一个更广泛的目标,这大概是这样的:
3-在LinAlg
,我们尝试创建一个通用的线性代数接口,该接口适用于在+
, *
, conj
下满足线性(等)的对象
这是一个非常酷的目标(肯定超出我所知道的任何其他编程语言/库),完全激发了递归adjoint
和2 (因为+
, *
和conj
本身都是递归的),这就是@ Sacha0的建议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建议,如果我们给permutedims
一个默认的第二个参数,我们根本不需要flip
,这也得到了有力的支持。
因此,现在的共识似乎是,对transpose
的唯一真正更改应该是在“幕后”:关于特殊的降低和懒惰与急切的评估,典型的最终用户可能不会知道或关心。 唯一真正可见的更改实际上只是拼写更改(使.'
贬值,并给permutedims
作为默认的第二个参数)。
因此,社区共识似乎在很短的时间内(几乎在@ Sacha0的帖子https://github.com/JuliaLang/julia/issues/20978#issuecomment-347360279的时候)改变了180度。 萨莎(Sacha)的帖子是如此雄辩,以至于只是改变了所有人的想法? (如果这样的话,那很好,我只是想了解为什么我们似乎朝着几天前我们都同意是错误的道路前进)。
我忘记了是否有人建议这样做,但是我们是否可以使transpose(::AbstractMatrix{AbstractMatrix})
(可能还有transpose(::AbstractMatrix{AbstractVector})
)递归,否则使transpose
非递归? 似乎它涵盖了所有基础,而且我想不出要让tranpose
递归使用的其他用例。
因此,社区共识似乎在很短的时间内(几乎在@ Sacha0的帖子#20978(评论)时)已改变了180度。 萨莎(Sacha)的帖子是如此雄辩,以至于只是改变了所有人的想法? (如果这样的话,那很好,我只是想了解为什么我们似乎朝着几天前我们都同意是错误的道路前进)。
如果我这么雄辩的话。 您看到的是共识实际上并未形成。 相反,(1)赞成现状但由于损耗而退出讨论的参与者返回表示意见; (2)在实践中没有考虑过脱离现状的其他各方(以及考虑释放的方式如何)形成了更赞成现状的观点,并表达了这一观点。
请考虑这种讨论已经持续了一个形式或其他在github从2014年开始,并有可能更早下线。 对于长期的参与者而言,这样的讨论变得精疲力竭且循环不断。 除了参与此讨论之外,还有其他有意义的工作可以做,例如编写代码,这更令人愉悦。 因此,对话在一个时期或另一个时期显得不平衡。 就个人而言,我即将达到这个损耗极限,所以我现在将重点放在编写代码上,而不是继续参与此讨论。 谢谢,最好! :)
我将对AbstractArrays进行非递归的转置和ctranspose投一小票,两者都递归于AbstractArray {T},其中T <:AbstractArray。
我同意在某些情况下递归行为是“正确的”,并且我认为问题在于我们如何在使用和开发软件包的人中以最小的惊喜实现正确的行为。
在此提议中,自定义类型的递归转置行为是选择加入的:通过将类型设为AbstractArray或定义适当的方法来选择加入
Base.transpose(AbstractArray{MyType})
或Base.transpose(AbstractArray{T}) where T<: MyAbstractType
。
我认为递归转置的鸭子输入策略(无需询问就可以递归)会产生一些意外,如上文所述。 如果您引入不同的ctranspose和adjoint,或更复杂的建议(例如conjadjoint和flip),则用户会遇到这些问题并尝试使用它们,而程序包维护人员将尝试支持它们。
举例来说,在新提案下很难支持的东西:正常,转置,ctranspose和conj数组都应具有与ReshapedArray和SubArray视图互操作的视图(或惰性评估)。 (我不知道它们是默认生成视图还是仅在使用@view
时生成视图。)这与降低A*_mul_B*
和带有标志“ N”的较低级别的BLAS调用有关,正如其他地方所指出的,“ T”和“ C”表示密集阵列。 如果他们将normal
, transpose
, ctranspose
和conj
在平等的基础上。 请注意,BLAS本身仅对正常情况支持'N',对转置支持'T',对ctranspose支持'C',并且对conj没有标志,我认为这是错误的。
最后,为了与高维数组和重塑形状保持一致,我认为对转置和ctranspose进行适当的概括是要反转所有维,即
transpose(A :: Array {T,3})= permutedims(A,(3,2,1))。
干杯!
我非常感谢实际从事这项工作的人们。 讨论过长的是向量伴随/转置(但绝不是递归方面),直到@andyferris加强并实现了它,并且效果很好。 同样,我也非常感谢正在进行的数组构造函数的重新设计。 对此表示赞许。
话虽如此,矩阵转置和伴随/ ctranspose从来没有得到太多的讨论,尤其是它的递归方面,它几乎是在https://github.com/JuliaLang/julia/pull/7244中作为单个动机块矩阵被无声介绍的。 。 事实证明,递归伴随的原因和动机多种多样,大多数人可以同意这是一个很好的选择,但不是唯一的选择。 但是,转置甚至没有一个单一的动机或实际用例。
这些讨论中发生了一些单独的事情,现在就需要我们可以快速实施的计划。
LinAlg
支持块矩阵(以及更多奇异结构)是否值得。 实现选择包括:根本没有递归的东西( +
, *
和conj
除外,因为那是Julia泛型函数的本质),递归所有内容(现状),或尝试进行某种类型检查或特征检查,以确定元素是否应执行递归线性代数或视为标量。transpose
, flip
,缩短了permutedims
语法(PR之所以首先提交,纯粹是因为它是实现最少的字符,甚至有道理(如果我们还执行其他操作),则对元素是否应进行递归转置进行某种类型检查或特征检查(也许甚至重新引入transpose(x::Any) = x
...)。x' * y -> Ac_mul_B(x, y)
,有点像疣,理想情况下在v1.0中不会存在。 除非我们能够支持快速的BLAS(没有多余的副本),否则这才被认为是不可行的,因此懒矩阵转置和伴随。LinAlg
的代码相当大,并且建立了许多年。 诸如矩阵乘法之类的许多东西都可以重构为对特征更友好,也许可以使用像新的broadcast
这样的调度系统。 我认为这是我们可以更轻松地将正确的数组(我认为是PermuteDimsArray的共轭重塑跨步矩阵的共轭视图的PermuteDimsArray)发送到BLAS的地方。 但是,这不会使v1.0成为可能,并且我们也在努力避免性能下降,而不会使代码变得更糟。 正如Sacha指出的(并且我正在发现),在元素上具有范围广泛的行为(递归伴随,递归转置,共轭,什么都没有)的转置视图会增加额外的复杂性,并提供了许多新的方法来使事情按其自身运行是。如果我们认为v1.0在某种程度上稳定了语言,那么从某种意义上说,改变行为的最大优先考虑就是第三点。 我会说:语言(包括解析器)应该是最稳定的,其次是Base
,其次是stdlib(可能包括或可能不包括LinAlg
,但是我想几乎肯定会包括BLAS
, Sparse
等一天)。 这一更改并不会真正影响用户(主要是库开发人员),因此,如果人们的意见在这里有所不同,我也不会感到惊讶。
当场在安迪! :)
我认为这里唯一要做的就是默认情况下让adjoint
和transpose
变得懒惰?
现在可以关闭吗?
下一步:“认真对待标量转置”
但是,认真地说,我们是否可以有一个良好的接口来指定PDE求解器中使用的不同3D转置和张量乘法? 有点严重,但我不确定是否可以处理此疯狂的下一次迭代。
没有
:)
我们可以有一个好的接口来指定在PDE求解器中使用的不同3D转置和张量乘法
绝对看起来像是包装的好主题。
一个很好的接口,用于指定不同的3D转置和张量乘法
TensorOperations.jl不会在这里做您需要的吗? (请注意,在这个级别上,“良好的接口”意味着类似于张量网络图,与TensorOperations的语法相比,用简洁的方式编写代码稍微有些挑战)。
是的,TensorOperations.jl看起来不错。 我在开玩笑,但是我从中得到了what。
最有用的评论
(OT:我已经很期待“认真对待7张量”,这是成功的6部分迷你剧集的下一部分...)