Julia: Levando a sério as transposições de vetor

Criado em 10 nov. 2013  ·  417Comentários  ·  Fonte: JuliaLang/julia

de @alanedelman :

Devemos realmente pensar cuidadosamente sobre como a transposição de um vetor deve despachar os vários métodos A_*op*_B* . Deve ser possível evitar novos tipos e matemática feia. Por exemplo, vetor'vetor produzindo um vetor (# 2472, # 2936), vetor 'produzindo uma matriz, e vetor' 'produzindo uma matriz (# 2686) são todos matemáticos ruins.

O que funciona para mim matematicamente (o que evita a introdução de um novo tipo) é que, para um Vector v unidimensional:

  • v' é um ambiente autônomo (ou seja, apenas retorna v ),
  • v'v ou v'*v é um escalar,
  • v*v' é uma matriz, e
  • v'A ou v'*A (onde A é um AbstractMatrix ) é um vetor

Uma transposição _N_-dimensional geral inverte a ordem dos índices. Um vetor, tendo um índice, deve ser invariante sob transposição.

Na prática, v' raramente é usado isoladamente e geralmente é encontrado em produtos de vetor de matriz e produtos de matriz de vetor. Um exemplo comum seria construir formas bilineares v'A*w e formas quadráticas v'A*v que são usadas em gradientes conjugados, quocientes de Rayleigh, etc.

A única razão para introduzir um novo tipo Transpose{Vector} seria representar a diferença entre os vetores contravariantes e covariantes, e não acho isso convincente o suficiente.

arrays breaking design linear algebra

Comentários muito úteis

BAM

Todos 417 comentários

Por exemplo, vetor'vetor produzindo um vetor (# 2472, # 2936), vetor 'produzindo uma matriz, e vetor' 'produzindo uma matriz (# 2686) são todos matemáticos ruins.

O duplo-dual de um espaço vetorial de dimensão finita é isomórfico a ele, não idêntico. Portanto, não tenho certeza de como isso é matemática ruim. É mais que tendemos a ignorar a distinção entre coisas que são isomórficas em matemática, porque o cérebro humano é bom em lidar com esse tipo de ambiguidade escorregadia e apenas fazer a coisa certa. Dito isso, concordo que isso deve ser melhorado, mas não porque seja matematicamente incorreto, mas porque é irritante.

Como pode v' == v , mas v'*v != v*v ? Faz mais sentido do que pensamos que x' * y seja seu próprio operador?

O duplo-dual de um espaço vetorial de dimensão finita é isomórfico a ele, não idêntico.

(Falando como eu agora) Não é apenas isomórfico, é naturalmente isomórfico, ou seja, o isomorfismo é independente da escolha da base. Não consigo pensar em uma aplicação prática para a qual valeria a pena distinguir entre esse tipo de isomorfismo e uma identidade. IMO, o fator de aborrecimento vem de fazer esse tipo de distinção.

Faz mais sentido do que pensamos que x' * y seja seu próprio operador?

Essa foi a impressão que tive da discussão desta tarde com @alanedelman.

Acho que o que Jeff está perguntando é acertado ... está começando a parecer que x'_y e x_y 'estão ganhando mais
sentido do que nunca.

Estou de acordo violento com @stefan. Matemática ruim não significava matemática errada, era
significava matemática irritante. Há muitas coisas que são tecnicamente corretas, mas não muito legais ...

Se seguirmos essa lógica, aqui estão duas opções que temos

x_x permanece um erro ..... talvez com uma sugestão "talvez você queira usar o ponto"
ou x_x é o produto escalar (não adoro essa escolha)

Se x e x' são a mesma coisa, então se você deseja que (x')*y signifique dot(x,y) isso implica que x*y também é dot(x,y) . Não há como escapar disso. Poderíamos fazer x'y e x'*y uma operação diferente, mas não tenho certeza se é uma boa ideia. As pessoas querem colocar isso entre parênteses da maneira óbvia e fazer com que funcione.

Eu diria que, se permitirmos que x*x signifique o produto escalar, basicamente não há como voltar atrás. Isso vai ser colocado no código das pessoas em todos os lugares e erradicá-lo será um pesadelo. Então, isomorfismo natural ou não, isso não é matemática pura e temos que lidar com o fato de que coisas diferentes em um computador são diferentes.

Aqui está uma discussão prática sobre como distinguir "tuplas ascendentes" e "tuplas descendentes" de que gosto:

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

Evita cuidadosamente palavras como "vetor" e "dual", talvez para evitar irritar as pessoas. No entanto, acho a aplicação a derivadas parciais atraente:

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

Outra razão para distinguir M[1,:] e M[:,1] é que atualmente nosso comportamento de transmissão permite este comportamento muito conveniente: M./sum(M,1) é estocástico de coluna e M./sum(M,2) é estocástico de linha . O mesmo poderia ser feito para a normalização se "consertássemos" a função norm para permitir a aplicação em linhas e colunas facilmente. Claro, ainda poderíamos ter matrizes de retorno sum(M,1) e sum(M,2) vez de vetores para cima e para baixo, mas isso parece um pouco estranho.

Gosto da ideia de vetores para cima e para baixo. O problema é generalizá-lo para dimensões superiores de uma forma que não seja completamente insana. Ou você pode simplesmente tornar os vetores um caso especial. Mas isso também parece errado.

É verdade que cima / baixo pode ser uma teoria separada. A abordagem para generalizá-los parece ser uma estrutura aninhada, que leva as coisas em uma direção diferente. Muito provavelmente há um motivo pelo qual eles não os chamam de vetores.

Além disso, x*y = dot(x,y) tornaria * não associativo, como em x*(y*z) vs. (x*y)*z . Eu realmente espero que possamos evitar isso.

Sim. Para mim, isso é totalmente inaceitável. Quer dizer, tecnicamente, * de ponto flutuante não é associativo, mas é quase associativo, enquanto isso seria apenas flagrantemente não associativo.

Todos concordamos que x*x não deve ser o produto escalar.

Resta saber se podemos pensar em v'w e v'*w como o produto escalar -
Eu realmente gosto de trabalhar dessa forma.

@JeffBezanson e eu estávamos conversando

Uma proposta é a seguinte:

v' é um erro para vetores (isso é o que o mathematica faz)
v'w e v'*w é um produto escalar (resultado = escalar)
v*w é uma matriz de produto externa (resultado = matriz)

Não há distinção entre vetores de linhas e colunas. Eu gostei disso de qualquer maneira
e estava feliz em ver o precedente do mathematica
De mathematica: http://reference.wolfram.com/mathematica/tutorial/VectorsAndMatrices.html
Devido à forma como o Mathematica usa listas para representar vetores e matrizes, você nunca precisa distinguir entre vetores de "linha" e "coluna"

Os usuários devem estar cientes de que não há vetores de linha .... ponto final.

Assim, se M é uma matriz

M[1,:]*v é um erro ..... (assumindo que vamos com M[1,:] é um escalar
O aviso pode sugerir tentar dot ou '* ou M[i:i,:]

M[[1],:]*v ou M[1:1,:]*v é um vetor de comprimento 1 (este é o comportamento atual de Julia, de qualquer maneira)

Sobre o problema intimamente relacionado em https://groups.google.com/forum/#!topic/julia -users / L3vPeZ7kews

O Mathematica compacta seções de matriz semelhantes a escalares:

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


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

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

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

Out[124]= {2, 2}

Out[125]= {2}

Out[126]= {1, 2}

[Editar: formatação de código - @StefanKarpinski]

@alanedelman

assumindo que vamos com M [1 ,:] é um escalar

você quer dizer que M [1 ,:] é apenas um vetor?

Sim, desculpe. Minha mente queria dizer que M [1 ,:] estava processando o escalar 1 :-)

O Mathematica usa o ponto final . ao invés do asterisco *
e então vai os 9 metros inteiros e transforma (vetor. vetor) em um escalar, exatamente o que estamos evitando
com o asterisco.

Sem dúvida, há muitos problemas com o período, um dos quais é que ele simplesmente não
parecido com o "ponto" em um produto escalar, e outro dos quais é que ele colide com
a leitura "op pontual" do ponto,

O Unicode fornece um caractere muito bom chamado "operador de ponto"
(char(8901)) que podemos imaginar oferecendo

então poderíamos ter (v ⋅ w) se tornando sinônimo de (v'*w)

Em resumo, um assunto atual da proposta para debate é

  1. A indexação escalar mata a dimensão, portanto
    A[i,:] é um vetor assim como A[:,i,j]
  2. A indexação do vetor é espessa
    A[ i:i , : ] ou A[ [i], : ] retorna uma matriz com uma linha
  3. v'w ou v'*w é o produto escalar para vetores (da mesma forma v*w' para produto externo)
  4. v' é indefinido para vetores (direcione o usuário para permutedims(v,1) ????)
  5. v*A retorna um vetor se A for uma matriz
  6. v⋅w também retorna o produto escalar (mas não vai tão longe quanto . da mathematica trabalhando em matrizes
  7. v*w é indefinido para vetores, mas um aviso pode alertar o usuário com boas sugestões, incluindo

As consequências são que

uma. se você se limitar a todos os vetores serem vetores de coluna, tudo funciona
b. se você fizer tudo uma matriz, tudo certamente funcionará, e é fácil tornar tudo uma matriz
c. se sua mente não consegue distinguir um vetor linha de uma matriz de uma linha, é provável que você seja educado
educadamente e graciosamente
d. Esta notação de ponto é agradável à vista

A sugestão 5) parece muito estranha para mim. Eu prefiro v'*A para que fique explícito que você está usando o vetor dual. Isso é particularmente importante em espaços vetoriais complexos, onde o dual não é apenas uma transformação de "forma".

Quero repetir a @StefanKarpinski que seria lamentável perder nosso comportamento conciso de transmissão em tudo isso. Após essa mudança, qual é a sintaxe concisa para pegar um vetor v e normalizar as colunas da matriz A por esses valores? Atualmente, pode-se usar A ./ v' . Isso é extremamente bom para manipulação de dados.

Boas perguntas

Meu esquema não impede v'*A pegar o conjugado complexo de v e multiplicar por A
e todos os vários outros casos que ainda não mencionei explicitamente, mas prontamente poderia

poderíamos eliminar 5
talvez seja desejável
não está de acordo com minha regra de vetor de coluna

Essa abordagem de transmissão é bonita e desajeitada
Uma solução agora é A ./ v[:,[1]]

Tem a vantagem de documentar qual dimensão está sendo transmitida no
e generaliza para matrizes dimensionais superiores

Ah, e a solução v[:,[1]] tem a virtude de NÃO pegar o conjugado complexo
que é provavelmente o que o usuário pretende ...

GOSTO DESTES DOIS EXEMPLOS porque o primeiro é um exemplo de ALGEBRA LINEAR
onde um conjugado complexo é desejado com muita frequência, mas o segundo exemplo é
um EXEMPLO DE DADOS MULTIDIMENSIONAIS onde queremos que as coisas funcionem em todas as dimensões
não apenas para matrizes, e muito provavelmente não queremos um conjugado complexo

requer # 552. Esta é a terceira vez que ele aparece nas últimas duas semanas.

Outra razão para distinguir M [1 ,:] e M [:, 1] é que atualmente nosso comportamento de transmissão permite este comportamento muito conveniente: M./sum(M,1) é coluna-estocástica e M./sum(M, 2) é estocástico de linha. O mesmo poderia ser feito para a normalização se "consertássemos" a função norma para permitir a aplicação em linhas e colunas facilmente. Claro, ainda poderíamos ter matrizes de retorno sum (M, 1) e sum (M, 2) em vez de vetores para cima e para baixo, mas isso parece um pouco errado.

Parece-me que, embora ter o comportamento de transmissão seja bom algumas vezes, você acaba tendo que apertar essas unidades extras com a mesma frequência. Portanto, ter que fazer o oposto algumas vezes está OK se o resto do sistema for melhor (e eu acho que ter as dimensões escalares removidas tornará o sistema mais agradável). Portanto, você precisará de uma função como

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

que permitirá código como M ./ widen(sum(M,2),2) ou A ./ widen(v,1) (veja o exemplo @blakejohnson acima)

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

Estou mais com @blakejohnson na questão da redução; Pessoalmente, acho que é mais claro para squeeze dimensões do que widen them. Eu suspeito que ficaria perpetuamente olhando os documentos para descobrir se widen insere a dimensão no índice indicado ou depois dele, e a numeração fica um pouco mais complexa se você quiser ampliar em várias dimensões de uma vez. (O que widen(v, (1, 2)) para um vetor v faz?) Nenhum desses são problemas para squeeze .

Independentemente de alargarmos ou espremermos por padrão, acho que Julia deveria seguir o exemplo de numpy quando se trata de expandir e permitir algo como v[:, newaxis] . Mas eu acredito que prefiro manter as dimensões em vez de descartá-las, é mais difícil detectar um bug em que você acidentalmente ampliou de maneira errada do que quando apertou da maneira errada (o que normalmente resultará em erro).

Na lista de @alanedelman
Eu sinto isso

v * A retorna um vetor se A for uma matriz

não é bom.

v_A deve ser um erro se A não for 1x1 (incompatibilidade do intervalo do índice)
v'_A deve ser a maneira correta de fazê-lo.

Uma maneira de lidar com esse problema é converter automaticamente o vetor v em matriz nx1 (quando necessário)
e sempre trate v 'como matriz 1xn (nunca converta isso para um vetor ou matriz nx1)
Também permitimos converter automaticamente a matriz 1x1 em um número de scaler (quando necessário).

Acho que isso representa uma maneira consistente e uniforme de pensar sobre álgebra linear. (boa matemática)

Uma maneira uniforme de lidar com todos esses problemas é permitir a conversão automática (tipo?) (Quando necessário)
entre matrizes de tamanho (n), (n, 1), (n, 1,1), (n, 1,1,1) etc (mas não entre matrizes de tamanho (n, 1) e (1, n) )
(Assim como convertemos automaticamente o número real em número complexo quando necessário)

Neste caso, uma matriz de tamanho (1,1) pode ser convertida em um número (quando necessário) (Veja # 4797)

Xiao-Gang (um físico)

Isso deixa v'_A no entanto .... Eu realmente quero que v'_A * w funcione

Minha impressão de álgebra linear em Julia é que ela é muito organizada como álgebra matricial, embora existam escalares e vetores verdadeiros (o que eu acho bom!)

Vamos considerar como lidar com um produto como x*y*z*w , onde cada fator pode ser um escalar, vetor ou matriz, possivelmente com uma transposição sobre ele. A álgebra de matrizes define o produto de matrizes, onde uma matriz tem o tamanho n x m . Uma abordagem seria estender esta definição de modo que n ou m possam ser substituídos por absent , que agiria como um valor de um no que diz respeito ao cálculo do produto , mas é usado para distinguir escalares e vetores de matrizes:

  • um escalar seria absent x absent
  • um vetor (coluna) seria n x absent
  • um vetor linha seria absent x n

Idealmente, gostaríamos de organizar as coisas de forma que nunca precisemos representar vetores de linha, mas seria o suficiente para implementar operações como x'*y e x*y' . Tenho a sensação de que esse é o tipo de esquema que muitos de nós buscamos.

Mas estou começando a suspeitar que banir os vetores de linha nesse tipo de esquema terá um custo alto. Exemplo: considere como precisaríamos colocar um produto entre parênteses para evitar a formação de um vetor linha em qualquer etapa intermediária: ( a é um escalar, u e v são vetores)

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

Para avaliar um produto x*y'*z evitando produzir vetores de linha, precisaríamos saber os tipos dos fatores antes de escolher a ordem de multiplicação! Se o próprio usuário fizer isso, parece um obstáculo à programação genérica. E não tenho certeza de como Julia poderia fazer isso automaticamente de uma maneira sã.

Outra razão para não corrigir a ordem de multiplicação com antecedência: parece que me lembro que houve uma ideia de usar a programação dinâmica para escolher a ordem de avaliação ideal de *(x,y,z,w) para minimizar o número de operações necessárias. Qualquer coisa que fizermos para evitar a formação de vetores de linha provavelmente interferirá nisso.

Então, agora, a introdução de um tipo de vetor transposto parece a alternativa mais sensata para mim. Isso, ou fazer tudo como agora, mas descartar as dimensões do singleton ao mantê-las resultaria em um erro.

A transposição é apenas uma maneira particular de permutar modos. Se você permitir v.' onde v é um vetor, então permutedims(v,[2 1]) deve retornar exatamente a mesma coisa. Ambos retornam um tipo de vetor de linha especial ou introduzem uma nova dimensão.

Ter um tipo especial para vetores de linha não parece uma boa solução para mim, porque o que você fará com outros tipos de vetores de modo-n, por exemplo, permutedims([1:4],[3 2 1]) ? Recomendo que você também leve em consideração a álgebra multilinear antes de tomar uma decisão.

@toivoh mencionou isso

"Uma abordagem seria estender esta definição para que n ou m pudesse ser substituído por ausente, que atuaria como um valor de um no que diz respeito ao cálculo do produto, mas é usado para distinguir escalares e vetores de matrizes:

  1. um escalar estaria ausente x ausente
  2. um vetor (coluna) seria nx ausente
  3. um vetor linha estaria ausente xn "

Em álgebra multilinear (ou para tensores de rand alto), a proposta acima corresponde ao uso ausente para representar
muitos índices do intervalo 1, ou seja, tamanho (m, n, ausente) podem corresponder a (m, n), (m, n, 1), (m, n, 1,1), etc.

Se usarmos essa interpretação de ausente, então 1. e 2. é bom e bom ter, mas 3. pode não ser bom.
Não queremos misturar matrizes de tamanho (1, n) e (1,1, n).

Não sou um especialista em teoria dos tensores, mas usei todos os sistemas mencionados acima (_sem_ quaisquer pacotes adicionais) para projetos substanciais envolvendo álgebra linear.

[TL; DR: pule para RESUMO]

Aqui estão os cenários mais comuns em que descobri a necessidade de maior generalidade no tratamento de matrizes do que operações comuns de vetores de matriz:

(1) Análise funcional: Por exemplo, assim que você está usando o Hessian de uma função com valor vetorial, você precisa de tensores de ordem superior para funcionar. Se você está escrevendo muito matemática, seria uma grande dor ter que usar uma sintaxe especial para esses casos.

(2) Controle de avaliação: Por exemplo, dado qualquer produto que pode ser calculado, deve-se ser capaz de computar qualquer subentidade desse produto separadamente, porque se pode desejar combiná-lo com várias subentidades diferentes para formar produtos diferentes. Assim, a preocupação de Toivo sobre, por exemplo, a*u' ser proibido não é apenas uma questão de compilação, mas de programação; uma variante ainda mais comum é o pré-cálculo de x'Q para calcular as formas quadráticas x'Q*y1 , x'Q*y2 , ... (onde isso deve ser feito sequencialmente).

(3) Código de simplificação: várias vezes ao lidar com operações aritméticas mapeadas em conjuntos de dados multidimensionais, descobri que 6-7 linhas de loop inescrutável ou código de mapeamento de função podem ser substituídas por uma ou duas operações de matriz breve, em sistemas que fornecem generalidade apropriada. Muito mais legível e muito mais rápido.

Aqui estão minhas experiências gerais com os sistemas acima:

MATLAB: A linguagem principal é limitada além das operações comuns de vetores de matriz, então geralmente acabam escrevendo loops com indexação.

NumPy: Capacidade mais geral do que MATLAB, mas confuso e complicado. Para quase todas as ocorrências de problemas não triviais, tive que consultar a documentação e, mesmo assim, às vezes descobri que precisava implementar alguma operação de array que, intuitivamente, deveria ter sido definida. Parece que há tantas ideias separadas no sistema que qualquer usuário e desenvolvedor terá problemas para adivinhar automaticamente como o outro pensará sobre algo. Geralmente é possível encontrar uma maneira curta e eficiente de fazer isso, mas nem sempre é óbvio para o escritor ou leitor. Em particular, eu sinto que a necessidade de alargamento e dimensões singleton apenas reflete uma falta de generalidade na implementação para aplicação de operadores (embora talvez alguns achem isso mais intuitivo).

Mathematica: Limpo e muito geral --- em particular, todos os operadores relevantes são projetados com o comportamento do tensor de ordem superior em mente. Além de Dot, consulte, por exemplo, os documentos sobre Transpose, Flatten / Partition e Inner / Outer. Ao combinar apenas essas operações, você já pode cobrir a maioria dos casos de uso de malabarismo de array e, na versão 9, eles ainda têm operações adicionais de álgebra de tensores adicionadas à linguagem central. A desvantagem é que, embora a maneira do Mathematica de fazer algo seja limpa e faça sentido (se você conhece a linguagem), pode não corresponder obviamente à notação matemática usual para fazê-lo. E, claro, a generalidade torna difícil saber como o código funcionará.

scmutils: Para análise funcional, é limpo, geral e fornece as operações mais matematicamente intuitivas (tanto escrever como ler) de qualquer uma das anteriores. A ideia de tupla para cima / para baixo é, na verdade, apenas uma extensão mais consistente e geral do que as pessoas costumam fazer na matemática escrita usando sinais de transposição, convenções de diferenciação e outras noções semipadronizadas; mas tudo funciona. (Para escrever minha tese de doutorado, acabei desenvolvendo uma notação consistente e inequívoca semelhante à notação matemática tradicional, mas isomórfica à sintaxe SICM de Sussman & Wisdom.) Eles também a usaram para uma implementação de geometria diferencial [1], que tem inspirou um porte para SymPy [2]. Eu não usei para análise de dados, mas eu esperaria que em um contexto de array genérico onde você só quisesse um tipo de tupla (como a Lista do Mathematica), você poderia escolher apenas um ("up") por convenção. Novamente, a generalidade obscurece as considerações de desempenho para o programador, mas espero que esta seja uma área em que Julia possa se destacar.

RESUMO

Acho que o tipo de vetor transposto proposto deve ser caracterizado como a tupla "descendente" mais geral em scmutils, enquanto os vetores regulares seriam as tuplas "ascendentes". Chamá-los de algo como "vetor" e "vetor transposto" provavelmente faria mais sentido para as pessoas do que chamá-los de "para cima" e "para baixo" (à custa da brevidade). Isso daria suporte a três categorias de uso:

(1) para análise de dados, se as pessoas querem apenas matrizes aninhadas, elas precisam apenas de "vetor";
(2) para álgebra linear matriz-vetor básica, as pessoas podem usar "vetor" e "vetor transposto" em correspondência direta com a convenção matemática ("matriz" seria equivalente a um "vetor transposto" de "vetor" s);
(3) para operações de tensor de ordem superior (onde há menos padronização e as pessoas geralmente têm que pensar de qualquer maneira), a implementação deve suportar a generalidade total do sistema aritmético de tupla de dois tipos.

Eu acredito que esta abordagem reflete o consenso emergente acima para os resultados de várias operações, com a exceção de que os casos em que as postagens anteriores consideraram erros ( v' e v*A ) seriam realmente significativos (e muitas vezes úteis ) resultados.

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

@thomasmcoffee soa como se você estivesse defendendo uma distinção explícita entre vetores co- e contravariantes.

Eu pensaria nisso como uma aplicação comum, mas excessivamente específica para o que estou defendendo: para mim, isso tem um significado geométrico que implica uma restrição a tensores retangulares de números (para representações de coordenadas). Uma vez que imagino (sem experiência nesta área) que uma biblioteca adequada de funções de álgebra tensorial com matrizes padrão normalmente seria suficiente para este propósito, simpatizo com o ponto de Alan de que isso por si só não é convincente o suficiente para introduzir um sistema de dois tipos em a linguagem central.

Estou pensando principalmente em outros aplicativos que dependem de uma estrutura aninhada mais geral, por exemplo, cálculo em funções de argumentos múltiplos de dimensionalidade mista, que seria mais difícil de desenvolver como um "complemento" posteriormente se a linguagem central não suportasse esta distinção. Talvez estejamos falando a mesma coisa.

O problema com os vetores para cima e para baixo é que você precisa estender a ideia para matrizes gerais. Caso contrário, os vetores se tornam algo especial e separado dos arrays, em vez de simplesmente o caso unidimensional de um array, o que levaria a uma confusão de problemas terríveis. Eu pensei muito sobre como fazer isso, mas não encontrei nada aceitável. Se você tiver boas ideias sobre como generalizar sensatamente os vetores para cima e para baixo em matrizes, adoraria ouvi-las.

Apenas tentando extrapolar essa ideia. Pelo que entendi, para usar uma matriz para calcular com vetores para cima e para baixo, você precisa indicar para cada dimensão se é para cima ou para baixo. Em geral, isso pode ser conseguido envolvendo uma matriz em algo como

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

onde UPMASK seria uma máscara de bits para indicar quais dimensões estão acima. Então as operações em arrays não embalados poderiam ser implementadas fornecendo um UPMASK padrão como uma função de N : os vetores seriam padronizados para um único up, matrizes para o primeiro up e o segundo down; então não tenho certeza de como seria razoavelmente continuado.

Alguns pensamentos aleatórios:

  • As formas quadráticas / bilineares seriam melhor representadas por duas dimensões inferiores?
  • Se tranpor corresponderia apenas a inverter o aumento / diminuição de cada dimensão, acho que também obteríamos um tipo de matriz transposta com a primeira dimensão para baixo e a segunda para cima.
  • Os padrões para cima que correspondiam ao padrão poderiam ser representados diretamente pela matriz subjacente em vez de envolvê-la.

Bem, esta é certamente uma generalização do tipo Transposed , e certamente tem algum mérito. Não tenho certeza se isso é viável.

Acho que a sugestão de Toivo é uma compreensão razoável do que eu estava defendendo acima. Para mim, o padrão mais sensato é continuar alternando direções em ordens superiores: por exemplo, se alguém fornecesse os componentes de uma série de potências como matrizes não embaladas, isso faria a coisa certa.

Mas, refletindo melhor, acho que poderia ser muito poderoso combinar as duas idéias: (1) distinções entre vetores para cima e para baixo e (2) distinções entre matrizes e vetores. Então você poderia ter objetos em que algumas dimensões são para cima, algumas para baixo e algumas são "neutras". A razão para distinguir matrizes de vetores é que, semanticamente, as matrizes são para organização (coletando várias coisas do mesmo tipo), enquanto os vetores são para coordenação (representando espaços multidimensionais). O poder de combinar as duas distinções em um objeto é que ele pode servir a ambos os propósitos simultaneamente. As dimensões neutras seriam tratadas de acordo com as regras de transmissão, enquanto as dimensões para cima / para baixo seriam tratadas de acordo com as regras aritméticas do tensor.

Voltando a um exemplo anterior meu, suponha que você esteja computando várias formas quadráticas x'Q*y1, x'Q*y2, ... para vetores diferentes y1, y2, ... . Seguindo o SICM, denote as tuplas ascendentes (vetores de coluna) por (...) e as tuplas descendentes (vetores de linha) por [...] . Se você quiser fazer tudo de uma vez, e você está preso apenas com cima / baixo, a maneira convencional seria combinar yi em uma matriz Y = [y1, y2, ...] usando uma tupla para baixo (de tuplas ascendentes) e calcula r = x'Q*Y , que fornece os resultados em uma tupla descendente r . Mas e se você quiser multiplicar cada um desses resultados por um vetor (coluna) v ? Você não pode simplesmente fazer r*v , porque você terá uma contração (produto escalar). Você pode converter r em uma tupla ascendente e, em seguida, multiplicar, o que fornece seus resultados em uma tupla ascendente (de tupla ascendente). Mas suponha que para a próxima etapa você precise de uma tupla para baixo. Semanticamente, você tem uma dimensão passando pelo seu cálculo que representa apenas uma coleção de coisas, que você sempre deseja transmitir; mas para conseguir isso no mundo estritamente para cima / para baixo, você precisa continuar fazendo conversões arbitrárias dependentes de contexto para obter o comportamento correto.

Em contraste, suponha que você também tenha tuplas neutras (matrizes), denotadas {...} . Então você naturalmente escreve ys = {y1, y2, ...} como um array (de tuplas acima), de forma que r = x'Q*ys é um array e r*v também é um array (de tuplas acima). Tudo faz sentido e nenhuma conversão arbitrária é necessária.

Stefan sugere que distinguir arrays 1-D de vetores up / down é desastroso, mas acho que esse problema é resolvido pelo fato de que a maioria das funções faz sentido operar em vetores _ou_ em arrays, mas NÃO _tanto_. (Ou, em matrizes _ou_ em matrizes de vetores _ou_ em vetores de matrizes _ou_ em matrizes de matrizes, mas NÃO _tanto_. E assim por diante.) Portanto, com as regras de conversão apropriadas, não pensei em um caso comum que não serviria a coisa certa automaticamente. Talvez alguém possa?

Ao olhar mais a fundo [1], descobri que scmutils realmente distingue o que eles chamam de "vetores" de tuplas para cima e para baixo sob o capô; mas atualmente as regras de conversão são configuradas de forma que esses "vetores" sejam mapeados para tuplas ascendentes (como eu havia proposto anteriormente) sempre que entrarem no mundo ascendente / descendente, com a ressalva de que "Nos reservamos o direito de alterar esta implementação para distinguir Vetores de esquema de tuplas para cima. " (Talvez alguém no campus possa perguntar ao GJS se ele tem alguma ideia específica em mente.) O sistema Sage [2] separa amplamente o manuseio de matrizes de vetores e matrizes (atualmente sem suporte central para tensores), e os únicos problemas que experimentei com isso tem a ver com sua falta de conversão embutida entre eles em casos que obviamente fariam sentido.

[1] http://groups.csail.mit.edu/mac/users/gjs/6946/refman.txt --- começando em "Objetos Estruturados"
[2] http://www.sagemath.org/

Eu estava conversando com @jiahao na mesa do almoço e ele mencionou que a equipe de Julia estava tentando descobrir como generalizar as operações de álgebra linear para matrizes dimensionais mais altas. Há dois anos, passei vários meses pensando nisso porque precisava para a KroneckerBio. Eu queria compartilhar minha abordagem.

Vamos considerar apenas o produto entre dois arrays por enquanto. Outras operações têm uma generalização semelhante. Os três tipos mais comuns de produtos ao lidar com matrizes são o produto externo, o produto interno e o produto elementwise. Normalmente pensamos em fazer operações como essa entre dois objetos, como inner(A,B) ou A*B . Ao fazer essas operações em matrizes de dimensões superiores, no entanto, elas não são feitas entre as matrizes como um todo, mas entre dimensões específicas das matrizes. Múltiplas suboperações externas / internas / elemento a elemento acontecem em uma única operação entre duas matrizes e cada dimensão de cada matriz deve ser atribuída a exatamente uma suboperação (explicitamente ou a um padrão). Para os produtos internos e elementwise, uma dimensão à esquerda deve ser emparelhada com uma dimensão de tamanho igual à direita. As dimensões externas do produto não precisam ser emparelhadas. Na maioria das vezes, o usuário está fazendo um produto interno ou um produto elementar entre um par de dimensões e um produto externo para todas as outras. O produto externo é um bom padrão porque é o mais comum e não precisa ser emparelhado.

Normalmente penso nas dimensões como sendo nomeadas em vez de ordenadas, como os eixos x, y e z de um gráfico. Mas se você deseja que os usuários possam realmente acessar os arrays por indexação ordenada (como A[1,2,5] vez de A[a1=1, a3=5, a2=2] ), você deve ter um procedimento consistente para ordenar os resultados de uma operação. Proponho ordenar o resultado listando todas as dimensões do primeiro array, seguido da listagem de todas as dimensões do segundo array. Quaisquer dimensões que participaram de um produto interno são espremidas e, para dimensões que participaram de um produto elementwise, apenas a dimensão do segundo array é espremida.

Vou fazer alguma notação para isso. Sinta-se à vontade para Juliafy it. Seja A uma matriz a1 por a2 por a3 e seja B uma matriz b1 por b2 . Digamos que array_product(A, B, inner=[2, 1], elementwise=[3, 2]) pegaria o produto interno entre as dimensões a2 e b1 , o produto elemento a elemento entre a3 e b2 , e o produto externo de a1 . O resultado seria um array a1 por a3 .

Deve ficar claro que nenhum operador binário ou unário terá muito significado no contexto de matrizes de dimensão superior. Você precisa de mais de dois argumentos para especificar o que fazer com cada dimensão. No entanto, você pode recuperar a facilidade da álgebra linear tornando os operadores do Matlab abreviações para operações de array apenas nas duas primeiras dimensões:

A*B do Matlab é array_product(A, B, inner=[2,1]) .

A.' do Matlab é permute(A, B, [2,1]) onde permute mantém inalteradas todas as dimensões mais altas do que a contagem do terceiro argumento.

Você pode escolher se vai ou não lançar erros quando a dimensionalidade dos arrays for maior que 2 ou mesmo diferente de 2, como o Mathematica faz com transposições vetoriais. Se você estiver usando apenas os cálculos gerais de array, você não precisa decidir se deve ou não seguir a sugestão de @wenxgwen de interpretar todos os arrays (n, m) como (n, m, 1) e (n, m, 1 , 1). Somente ao usar os operadores de álgebra linear ou outros operadores que esperam matriz ou dimensionalidade particular, você deve tomar essa decisão. Eu gosto da sugestão de @wenxgwen , porque há poucas desvantagens em uma linguagem digitada dinamicamente.

Eu escrevi uma descrição mais

Obrigado pela perspectiva! Achei isso bastante esclarecedor para entender que tipo de animal um produto de array * array geral realmente é.

Pode ser interessante cruzar as propostas de multiplicação de matrizes multidimensionais com a semântica proposta para um operador de multiplicação de matrizes no PEP 0465 . Em particular:

As entradas de vetor 1d são promovidas a 2d adicionando ou acrescentando um '1' à forma, a operação é executada e, em seguida, a dimensão adicionada é removida da saída. O 1 é sempre adicionado "fora" da forma: prefixado para argumentos à esquerda e acrescentado para argumentos direitos. O resultado é que matrix @ vector e vector @ matrix são ambos legais (assumindo formatos compatíveis) e ambos retornam 1d vetores; vector @ vector retorna um escalar ... Uma infelicidade desta definição para vetores 1d é que torna @ não associativo em alguns casos ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2)). Mas este parece ser um caso em que a praticidade supera a pureza

Esquivar-se da digitação em Python causa um problema especial. Naturalmente, arrays e matrizes devem ser intercambiáveis ​​(mesmos dados subjacentes). Mas, como o Python desencoraja a verificação de um tipo, as matrizes não são convertidas para a interface correta no início de uma função que espera uma matriz e vice-versa. É por isso que eles devem ter caracteres de operador diferentes. Julia com verificação de tipo em tempo de execução e métodos convert não sofre dessa ambigüidade.

Do PEP 0465:

Uma infelicidade desta definição para vetores 1d é que ela torna @ não associativo em alguns casos ((Mat1 @ vec) @ Mat2! = Mat1 @ (vec @ Mat2))

Notavelmente, este tipo de definição pode produzir resultados incorretos no Mathematica, porque Dot ( . ) é considerado associativo ( Flat ) quando avaliado simbolicamente (como com f abaixo, mas não com g ):

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

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

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

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

De @drhagen :

Julia com verificação de tipo em tempo de execução e métodos convert não sofre dessa ambigüidade.

É por isso que acho que a solução certa para Julia _deve_ deixar os próprios dados distinguirem entre semântica semelhante a array (para transmissão universal) e semântica semelhante a tensor (para possível contração).

Não sou de forma alguma uma autoridade aqui, mas não acho que o tipo de coleção de dimensão arbitrária geral ( Array ) deva oferecer suporte a um operador que faz produto escalar. Este operador simplesmente não pode ser definido de forma sensata para este tipo porque o produto escalar pode estar entre quaisquer duas dimensões, exigindo argumentos adicionais que não podem ser fornecidos a um operador binário. O mesmo vale para todas as operações de álgebra linear, inv , transpose , etc.

Para operar no campo matemático da álgebra linear, deve haver mais três tipos, Matrix , ColumnVector e RowVector , nos quais todos os operadores e funções normais da álgebra linear trabalhar normalmente.

Agora que a estrutura de tipo está bem definida, você pode tornar mais fácil para o usuário adicionando a conversão implícita de Matrix a Array{2} , ColumnVector a Array{1} , e RowVector a Array{2} (não tenho certeza sobre este), Array{2} a Matrix e Array{1} a ColumnVector .

Minha proposta acima (https://github.com/JuliaLang/julia/issues/4774#issuecomment-32705055) permite que cada dimensão de uma estrutura multidimensional distinga se tem neutro ("coleção" / "matriz"), para cima (" coluna ") ou semântica para baixo (" linha "). Acho que o que você está descrevendo é um caso especial.

A vantagem dessa abordagem geral é que mesmo em cálculos com muitas dimensões de dados ou espaço, você pode fazer com que os operadores façam a coisa certa sem especificar explicitamente em quais dimensões devem operar. Acho que concordamos que, pelo menos em Julia, é muito mais intuitivo e legível para um usuário especificar uma vez o significado dos dados de entrada escolhendo parâmetros de tipo, do que ter que especificar o significado de cada operação chamando cada instância com argumentos adicionais que fornecem índices dimensionais. Conversões implícitas ou explícitas ainda podem ser usadas, com generalidade dimensional total, nos casos em que o significado deve ser alterado no meio do caminho de maneiras incomuns.

@thomasmcoffee Gosto muito da sua proposta. Implementei algo vagamente semelhante em uma DSL (muito tempo atrás, muito longe) com alguns princípios orientadores (também conhecidos como opiniões pessoais):

  1. A noção de duais como distintos é vital para qualquer semântica sensivelmente autoconsistente.
  2. A aplicação ad hoc de álgebra tensorial com operadores parametrizados (ou qualquer coisa externa aos dados) é esteticamente muito desagradável.

As maiores reclamações que recebi naquela época (e elas eram altas) eram exatamente sobre o tipo de inconveniente que sua semântica trivalente (adicionando uma noção de coleção neutra) resolve. Agradável! Essa ideia nunca me ocorreu, mas faz muito sentido agora que você a divulgou. Eu realmente gostaria de usar esse sistema, e quero dizer, para um trabalho real. Seria bom se Julia pudesse acomodar isso!

O que vocês parecem estar descrevendo são tensores regulares. Duvido que seja um caso de uso comum o suficiente para justificar estar na biblioteca padrão, já que os outros dois casos de uso (coleções e álgebra linear) são muito mais comuns. No entanto, se pudesse ser integrado perfeitamente, eu apoiaria. Você poderia dar alguns exemplos do que algumas operações comuns olhariam neste sistema, como multiplicação de matriz de vetor, multiplicação de matriz escalar, distribuição da adição de uma matriz sobre uma matriz de matrizes, etc.?

Acho que você está certo, David. Na verdade, estamos falando de dois casos de uso.

O subconjunto da Álgebra Linear é mais comumente necessário para a maioria das pessoas enquanto você
dizer. Mesmo aí eu defendo manter uma distinção entre v e v '.

O que eu realmente gostaria (insira a divulgação da ganância aqui) é Tensores com
status de primeira classe (ou próximo) ... próximo à velocidade nativa (no caso limite,
em comparação com o desempenho da Álgebra Linear) com sintaxe fácil, sem modificações
problemas de digitação, com co / contravariância codificada nos dados, não imposta
os operadores. Depois de definir a semântica dos dados, as operações devem
apenas trabalhe. Digitação de pato tensoral.

Talvez tensores e TDT pertençam a um pacote e não ao núcleo, apenas em
motivos de popularidade relativa. Mas como a declaração de Julia de
independência diz, Julia nasce da ganância. E como Gordon Gecko disse,
a ganância é boa. :)
Em 21 de março de 2014, 03:14, "David Hagen" [email protected] escreveu:

O que vocês parecem estar descrevendo são tensores regulares. Eu duvido que seja um
caso de uso comum o suficiente para justificar estar na biblioteca padrão, como
os outros dois casos de uso (coleções e álgebra linear) são muito mais
comum. No entanto, se pudesse ser integrado perfeitamente, eu apoiaria.
Você poderia dar alguns exemplos de como seriam algumas operações comuns
sob este sistema, como multiplicação vetor-matriz, matriz escalar
multiplicação, distribuindo a adição de uma matriz sobre uma matriz de
matrizes, etc.?

Responda diretamente a este e-mail ou visualize-o em Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -38262998
.

Eu acho que a integração perfeita é definitivamente alcançável dada uma família de tipos rica o suficiente. Estendendo https://github.com/JuliaLang/julia/issues/4774#issuecomment -32693110 de Toivo acima, pode começar assim:

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

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

(atualmente AbstractMatrix{T} simplesmente aliases AbstractArray{T, 2} ; potencialmente, outro nome poderia ser usado aqui)

A partir daqui, as seguintes implementações parecem lógicas:

  1. O método generalizado transpose , depois de reorganizar as dimensões e os índices UPMASK e DOWNMASK correspondentes, troca UPMASK e DOWNMASK. As dimensões neutras não seriam afetadas.
  2. Quaisquer AbstractArray{T, N} subtipos são tipicamente convertidos por padrão em AbstractTensorArray{T, N, [..., false, true, false, true], [..., true, false, true, false]} subtipos alternados em operações de tensor. Isso preserva a semântica existente da sintaxe de array especial de Julia para vetores e matrizes.
  3. Um método construtor (digamos, array ) para AbstractTensorArray é usado para produzir dimensões neutras e pode combinar outros AbstractTensorArray s (ou tipos conversíveis para eles) para criar um AbstractTensorArray combinado

Considerando os exemplos de @drhagen :

multiplicação de matriz de vetor, multiplicação de matriz escalar

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

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

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

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

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

(usando Matrix com uma definição correspondente à AbstractMatrix definição acima)

distribuir a adição de um array sobre um array de arrays

Eu entendo que isso significa, semanticamente, a adição de um vetor sobre uma matriz de vetores, adição de uma matriz sobre uma matriz de matrizes, e assim por diante:

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

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

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

Considerando meu exemplo anterior de escalar um vetor v por várias formas quadráticas diferentes x'M*w1, x'M*w2, ... , para um resultado final x'M*w1*v, x'M*w2*v, ... :

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

Nesta implementação nocional, presumi que AbstractArray é deixado sozinho e, portanto, AbstractTensorArray forma seu próprio "espaço" na hierarquia de tipos. As coisas poderiam ser simplificadas se toda a família AbstractArray fosse simplesmente substituída por AbstractTensorArray , mas isso é outra discussão.

No contexto de um pacote para algo em física quântica, tenho tentado definir meu próprio tipo de tensor (na verdade, mais de um). Inicialmente, eu também tive alguma noção de índices vindo em dois sabores (para cima e para baixo, entrada e saída, covariante e contravariante, como você quiser chamá-lo), com essas informações sendo armazenadas em um campo do tipo ou mesmo em um parâmetro de tipo. Depois de um tempo, decidi que isso era um grande aborrecimento. É muito mais fácil apenas associar os índices do tensor a um espaço vetorial (o que eu já estava fazendo de qualquer maneira) e permitir que esse espaço vetorial tenha um dual que é diferente. Na prática, por espaço vetorial, quero dizer apenas um tipo simples de Julia que envolve a dimensão do espaço e se é dual ou não. Se um índice tensorial estiver associado a um espaço vetorial normal, é um índice ascendente; se estiver associado a um índice dual, é um índice descendente. Você quer trabalhar com tensores / matrizes para os quais não há distinção, basta definir um tipo de espaço vetorial diferente que não distingue entre o espaço vetorial normal e seu dual.

Nesta proposta, você só pode contrair índices tensoriais que estão associados a espaços vetoriais que são duais entre si. ctranspose (= conjugação hermitiana) mapeia o espaço vetorial de cada índice em seu dual (junto com a permutação dos índices no caso de uma matriz, e uma definição preferida para tensores de ordem superior) etc.

Claro, a transposição normal e a conjugação complexa não são realmente bem definidas neste cenário (ou seja, estes não são conceitos independentes de base)

Minimalisticamente, é mais ou menos assim:

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

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

Claro, você poderia inventar alguma sintaxe para não ter que escrever Space constantemente. Você pode criar um tipo de espaço diferente para o qual dual (s) == s para ter tensores que não distinguem entre índices para cima e para baixo, e assim por diante. Mas é claro que não há como isso ser incorporado ao tipo Array padrão de Julia sem quebrar tudo ...

Sempre me perguntei por que não existe uma relação mais próxima entre como os tensores são usados ​​em engenharia / física e como eles são tratados em programas de software matemático. Achei uma conversa de troca de pilha interessante sobre o tópico ... http://math.stackexchange.com/questions/412423/differences-between-a-matrix-and-a-tensor. Aqui, também, foi um bom posto de referência.
http://www.mat.univie.ac.at/~neum/physfaq/topics/tensors

Eu uso muito o Matlab para minha computação científica diária, mas sou um novato em Julia. Aqui, noto que há muitas discussões sobre multiplicação e transposição de matrizes de alta dimensão ou outras operações de matrizes semelhantes. Sugiro que dê uma olhada em http://www.mathworks.com/matlabcentral/fileexchange/8773-multiple-matrix-multiplications--with-array-expansion-enabled

Ele basicamente segue a sintaxe semelhante ao que @drhagen mencionou em um post anterior, como array_product (A, B, inner_A_dim = [1, 2], inner_B_dim = [3, 4]) para um produto entre os arrays A e B em as dimensões internas fornecidas.

Este é um pacote Matlab que pode aplicar multiplicações ou operações de transposição em algumas dimensões selecionadas. Há um manual no pacote sobre como implementar essas operações no Matlab, mas acho que a teoria matemática deve se aplicar a outras linguagens também. A ideia deles é implementar operações de array evitando o uso de For-loops e contando principalmente com a remodelagem de array e assim por diante. Portanto, é extremamente rápido no Matlab. Não sei se Julia gosta mais de operações vetorizadas ou de operações desvectorizadas (parece a última). Eu sinto que a operação vetorizada é uma vantagem para operações de array de alta dimensão, se o núcleo for compatível. Talvez devêssemos considerar sinceramente esse tipo de operação de array neste estágio.

Para sua referência: Outra implementação semelhante em Matlab para operação INV está aqui: http://www.mathworks.com/matlabcentral/fileexchange/31222-inversion-every-2d-slice-for-arbitrary-multi-dimension-array

Observe também que, após o lançamento do pacote de suporte à operação de matrizes Matlab em 2005, o registro de download é mantido em um alto nível até hoje. Como na minha experiência prática, as operações de array são usadas com muita frequência na física e em outras áreas. Eu diria que, se Julia tiver funções semelhantes para operar arrays com tamanhos arbitrários, o jogo ficará muito interessante!

outra votação aqui para a solução proposta de @alanedelman para o topo. aqui está um exemplo motivador.

agora, uma fatia de linha é uma matriz 2d, enquanto uma fatia de coluna é uma matriz 1d; que é estranhamente assimétrico e feio:

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

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

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

em particular, significa que não posso multiplicar uma linha por uma coluna e extrair um número sem fazer alguma manipulação terrivelmente feia, como

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

Não é uma proposta coerente a menos que você faça de '* um operador especial, o que é bastante duvidoso, visto que x'*y e (x')*y não significam a mesma coisa. Além disso, tornaria a multiplicação não associativa.

eu entendo as dificuldades com x_y 'e y'_x. Pode ser melhor tratar
produtos internos e externos como operações separadas, usando, por exemplo, ponto (). (Possivelmente
também usando cdot?)

Mas quais são os argumentos a favor de ter uma fatia ao longo do primeiro
dimensão retorna um objeto cuja dimensão é diferente de uma fatia ao longo
a segunda dimensão? Para consistência, parece que cada vez que você fatia,
a dimensão do objeto resultante deve ser diminuída em um.

Na quarta-feira, 16 de julho de 2014 às 20h17, Stefan Karpinski [email protected]
escrevi:

Não é uma proposta coerente a menos que você faça '* um operador especial, que
é bastante duvidoso, visto que x'_y e (x ') _ y não significam a mesma coisa.
Além disso, tornaria a multiplicação não associativa.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -49254346.

Madeleine Udell
Candidato a Doutorado em Engenharia Computacional e Matemática
Universidade de Stanford
www.stanford.edu/~udell

@madeleineudell , concordo com você, mas esse é um problema diferente, consulte # 5949. Embora essa questão pareça encerrada, não me lembro de haver um acordo ou conclusão clara.

Assim que mudarmos para as visualizações de matriz, será mais fácil explorar essas direções. Em particular, dizer slice(A, i, :) fornece o comportamento que você deseja. (Ele faz isso agora, mas ao custo da introdução de um tipo mais lento, o SubArray.)

De um ponto de vista puramente matemático, todas as questões apresentadas aqui vêm de uma fusão (e confusão) entre o que entendemos por matrizes e o que entendemos por vetores / tensores / matrizes. Arrays, conceitualmente, são simplesmente listas (ou, no caso de arrays n-dim, listas de listas). Como tal, não há especificação natural para operações como multiplicação de matriz, transposição, etc. Enquanto funções como permutados, operações de elemento inteligente e operações específicas de eixo (média, mediana, etc.) fazem sentido e podem ser definidas exclusivamente em um maneira natural, operações como produtos escalares não podem ser.

Como mencionado acima, vetores e tensores são objetos geométricos e, embora seja possível representá-los usando matrizes, essas representações não contêm a mesma riqueza de estrutura dos objetos matemáticos que representam. A transposição de um array 1-dim é um ambiente autônomo; a transposta de um vetor é sua dual. A transposição de uma matriz 2-dim pode ser definida de forma única e natural como a permutação de suas dimensões, mas isso geralmente não é verdade para tensores: enquanto o caso natural é válido para tensores de classificação (1,1) (também conhecido como matrizes), um tensor de posto (2,0) transpõe em um tensor de posto (0,2). Novamente, tratando tensores como matrizes, a informação geométrica que torna tensores tensores é perdida.

Isso é importante ao definir operações como produtos escalares. Um produto escalar tem um significado geométrico específico (a projeção de um vetor no espaço dual definido por um segundo vetor) e, portanto, uma definição consistente de produtos escalar requer a preservação da informação geométrica contida nos vetores. O uso de certas suposições pode tornar possível o uso de matrizes e ainda cobrir a maioria dos casos de uso, mas essas suposições são confusas (como visto pelas várias propostas neste tópico) e na verdade tornam as coisas mais difíceis para quem precisa de uma estrutura mais rica de tensores .

Portanto, considere este um voto forte a favor da sugestão da thomasmcoffee para incluir um tipo de AbstractTensor mais rico. Minha preferência pessoal seria que operações como transposição e produtos escalares não fossem nem mesmo definidas para matrizes, mas como suspeito que a maioria das pessoas não compartilharia dessa visão, eu gostaria pelo menos a capacidade de criar tensores verdadeiros caso fosse necessário.

A implicação prática dessa perspectiva parece ser que os arrays devem ser identificados com um subconjunto de tensores, e a transposição de um array 1-d deve resultar em DualVector ou talvez um erro. Minha opinião é que isso é análogo a operações em números reais que fornecem números complexos.

Minha perspectiva seria que a família AbstractArray geral, um contêiner de dados (multidimensional), é suficientemente geral para ser uma parte indespensável de qualquer linguagem de programação técnica. Um tensor seguindo regras matemáticas estritas, embora eu goste muito dele, é um bom objeto para um pacote dedicado. Na verdade, estou trabalhando em algo semelhante ao especificado por https://github.com/Jutho/TensorToolbox.jl . Até agora não está documentado e em grande parte não foi testado. Eu o escrevi para as coisas de que preciso pessoalmente na física quântica de muitos corpos, mas espero que seja construído de uma forma que seja suficientemente geral e extensível para ser útil para a grande comunidade de matemáticos e físicos que se preocupam em trabalhar com tensores.

Para dar alguns detalhes (copiado do fórum JuliaQuantum): Decidi definir uma nova hierarquia de tipo para tensores, que é independente do tipo AbstractArray de Julia (embora o Tensor básico seja apenas um envoltório para Array). Supõe-se que essa hierarquia de tipo funcione de uma maneira um pouco mais formal. Os índices tensores estão associados a espaços vetoriais (doravante referidos como espaços de índice), e se o tipo de espaço vetorial ao qual o índice tensorial está associado for diferente de seu dual, isso corresponde a um tensor que distingue entre índices covariantes e contravariantes.

Portanto, a primeira parte do pacote é a parte abstrata para definir os espaços vetoriais, onde vou combinar a hierarquia de tipos dos objetos Julia com a hierarquia matemática dos espaços vetoriais. Um espaço vetorial geral V vem em quatro variedades, correspondendo à teoria da representação do grupo linear geral em V, ou seja, o próprio V (representação fundamental), conj (V), dual (V) e dual (conj (V)). Para espaços vetoriais reais, conj (V) = V e há apenas V e dual (V), correspondendo aos vetores contravariantes e covariantes. Depois, há os espaços de produto interno e, no nível superior da hierarquia, os espaços euclidianos, que são espaços de produto interno com um produto interno euclidiano padrão (ou seja, base ortogonal). Em física, também é útil pensar sobre espaços vetoriais que são decompostos em diferentes setores, ou seja, são classificados por, por exemplo, representações irredutíveis de ações de simetria.

Tensores são objetos que vivem em (um subespaço do) produto tensorial de alguns espaços vetoriais elementares. No entanto, além do Tensor padrão, que é um objeto que vive no espaço do produto tensorial de seus espaços de índice, pode-se construir tensores que vivem, por exemplo, no setor invariante de um produto tensorial de espaços graduados por irreps, o subespaço simétrico ou anti-simétrico de um produto tensorial de espaços idênticos, ... Pode-se ter espaços vetoriais fermiônicos como espaços índices, o que implica que uma permutação dos índices tensores induzirá certas mudanças de sinal dependendo dos setores de paridade, etc ...

Então, deve haver certas operações definidas em tensores, a mais importante das quais é tensores de contração, mas também, por exemplo, fatorações ortogonais (decomposição de valor singular) etc. Finalmente, deve haver mapas lineares que mapeiam um tensor em outro. Eles merecem um tipo especial em que normalmente não se deseja codificá-los totalmente como matriz, mas sim de uma maneira que o produto do vetor da matriz possa ser calculado com eficiência, para uso em métodos iterativos (Lanczos etc). Meus dois pacotes existentes até agora (TensorOperations.jl e LinearMaps.jl) implementam essa funcionalidade para Arrays padrão, a caixa de ferramentas de tensor em construção os sobrecarregaria / redefiniria para a nova hierarquia AbstractTensor.

Espero que este pacote seja suficientemente geral para que também seja útil para a comunidade física / matemática mais ampla. Por exemplo, se surgir alguém que crie um pacote para trabalhar com variedades, ele pode definir um espaço vetorial TangentSpace como subespaço do InnerProductSpace abstrato e pode criar imediatamente tensores vivendo no produto tensorial de alguns espaços tangentes e cotangentes. Na verdade, estou pensando em dividir a parte para definir espaços vetoriais em um pacote separado, que poderia crescer em um pacote para definir estruturas / objetos matemáticos.

Finalmente, a interoperabilidade com julia padrão vem de chamar tensor em um Array padrão, que o envolve em um objeto do tipo Tensor com os índices associados a espaços do tipo CartesianSpace . Este é o espaço vetorial real padrão R ^ n com produto euclidiano, onde não há distinção entre índice covariante e contravariante. Acho que isso significa melhor o que é uma Julia Array padrão.

@JeffBezanson , sou ambivalente em relação ao tratamento de matrizes como subconjuntos de tensores. Nenhuma informação é perdida dessa maneira, mas ao mesmo tempo existem várias interpretações possíveis para matrizes, e a interpretação do tensor nem sempre (ou mesmo geralmente) faz sentido. Considere as imagens: uma imagem pode ser considerada um campo de valor vetorial em uma variedade (normalmente 2d). Restringir esse campo a uma grade retangular fornece uma estrutura que, naturalmente, você gostaria de representar usando um array 3d. No entanto, na verdade, este é apenas um mapeamento do espaço de pontos da grade no espaço vetorial {R, G, B}, então o significado geométrico das primeiras duas dimensões (os rótulos xey da grade) é diferente do significado geométrico da terceira dimensão (que é, na verdade, um vetor).

Não me oponho à sugestão de @Jutho de dividir a mecânica tensorial em um pacote separado. Ele provavelmente está certo ao dizer que o número de usuários que precisam da mecânica de tensor completa é muito menor do que o número de pessoas que querem apenas operações diretas de array. A pergunta que realmente estamos tentando fazer aqui é "em que domínio a álgebra linear deve cair?"

A maquinaria da álgebra linear é um subconjunto suficientemente substantivo da maquinaria da álgebra tensorial que, pelo menos em minha mente, não faz sentido implementar a primeira sem também implementar a última. Operações como v'M são mais concisa e consistentemente representadas se tivermos uma noção de vetores co- e contravariantes, mas isso já nos coloca a maior parte do caminho para as operações tensoriais gerais.

Concordo com você que isso é conceitualmente semelhante a operações com números reais que retornam números complexos.

Considere as imagens: uma imagem pode ser considerada um campo de valor vetorial em uma variedade (normalmente 2d). Restringir esse campo a uma grade retangular fornece uma estrutura que, naturalmente, você gostaria de representar usando um array 3d. No entanto, na verdade, este é apenas um mapeamento do espaço de pontos da grade no espaço vetorial {R, G, B}, então o significado geométrico das primeiras duas dimensões (os rótulos xey da grade) é diferente do significado geométrico da terceira dimensão (que é, na verdade, um vetor).

Embora isso não aborde ou afaste sua mensagem geral, https://github.com/timholy/Images.jl/pull/135 está trabalhando para implementar essa ideia para imagens. Espero que isso também torne mais fácil lidar com tensores de estrutura de cores , que estou procurando usar para um projeto.

Em 23 de agosto de 2014, às 20:36, jdbates [email protected] escreveu:

@JeffBezanson , sou ambivalente em relação ao tratamento de matrizes como subconjuntos de tensores. Nenhuma informação é perdida dessa forma, mas ao mesmo tempo existem múltiplas interpretações possíveis para as imagens, e a interpretação do tensor nem sempre (ou mesmo normalmente) faz sentido. Considere as imagens: uma imagem pode ser considerada um campo de valor vetorial em uma variedade (normalmente 2d). Restringir esse campo a uma grade retangular fornece uma estrutura que, naturalmente, você gostaria de representar usando um array 3d. No entanto, na verdade, este é apenas um mapeamento do espaço de pontos da grade no espaço vetorial {R, G, B}, então o significado geométrico das primeiras duas dimensões (os rótulos xey da grade) é diferente do significado geométrico da terceira dimensão (que é, na verdade, um vetor).

Eu concordo que os tensores não substituem os arrays. Este exemplo acima é de fato uma estrutura matemática diferente (ou seja, um pacote vetorial ou, mais geralmente, um pacote tensorial), cuja representação também pode ser dada como uma matriz multidimensional escolhendo uma grade para as coordenadas múltiplas e uma base para a parte do espaço vetorial. Então, de fato, você pode ter diferentes objetos / estruturas matemáticas que são bem definidos de uma forma independente de coordenada / independente de base, mas que podem ser representados (após escolher um sistema de coordenadas ou uma base) como uma matriz multidimensional. Portanto, matrizes multidimensionais certamente não se restringem a representar tensores. O contrário também falha, pois nem todos os tensores têm uma representação conveniente usando um array multidimensional. Esse é apenas o caso quando você usa uma base particular conhecida como base do produto, que é obtida tomando o produto direto de todas as combinações possíveis dos vetores de base individuais dos espaços vetoriais envolvidos no espaço do produto tensorial. Em alguns casos, por exemplo, ao usar tensores em um subespaço invariante de simetria do espaço do produto tensorial, tal representação não é mais possível e você precisa definir uma base diferente para o espaço completo, em relação ao qual o tensor é apenas representado como uma longa lista unidimensional de números.

Não me oponho à sugestão de @Jutho de dividir a mecânica tensorial em um pacote separado. Ele provavelmente está certo ao dizer que o número de usuários que precisam da mecânica de tensor completa é muito menor do que o número de pessoas que querem apenas operações diretas de array. A pergunta que realmente estamos tentando fazer aqui é "em que domínio a álgebra linear deve cair?"

A maquinaria da álgebra linear é um subconjunto suficientemente substantivo da maquinaria da álgebra tensorial que, pelo menos em minha mente, não faz sentido implementar a primeira sem também implementar a última. Operações como v'M são mais concisa e consistentemente representadas se tivermos uma noção de vetores co- e contravariantes, mas isso já nos coloca a maior parte do caminho para as operações tensoriais gerais.

Concordo com você que isso é conceitualmente semelhante a operações com números reais que retornam números complexos.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

existem várias interpretações possíveis para matrizes, e a interpretação do tensor nem sempre (ou mesmo normalmente) faz sentido. Considere as imagens: uma imagem pode ser considerada um campo de valor vetorial em uma variedade (normalmente 2d). Restringir esse campo a uma grade retangular fornece uma estrutura que, naturalmente, você gostaria de representar usando um array 3d. No entanto, na verdade, este é apenas um mapeamento do espaço de pontos da grade no espaço vetorial {R, G, B}, então o significado geométrico das primeiras duas dimensões (os rótulos xey da grade) é diferente do significado geométrico da terceira dimensão (que é, na verdade, um vetor).

Era exatamente esse tipo de distinção que eu estava tentando capturar na proposta nocional AbstractTensorArray de https://github.com/JuliaLang/julia/issues/4774#issuecomment -38333295, permitindo o tipo array e tensor -como dimensões. Sob este esquema, espero representar o seu exemplo como

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

de modo que as dimensões x, y e RGB sejam "para baixo", "para cima" e "neutras", respectivamente. As operações geométricas (por exemplo, transformações afins) poderiam então lidar com as dimensões das coordenadas da grade em forma de tensor enquanto mapeavam os valores RGB em forma de matriz. (Se mais tarde você quiser tratar os valores RGB geometricamente, terá que alterar explicitamente a máscara para esse propósito, mas acho que (a) é menos comum que dois tipos diferentes de operações geométricas sejam aplicados a diferentes subespaços de a mesma tabela de dados e (b) nesta situação, uma conversão explícita provavelmente _melhoraria_ a clareza do código.)

Eu não havia considerado as representações conjugadas que @Jutho menciona, mas parece-me que essa generalização poderia ser tratada estendendo ainda mais a mesma abordagem de mascaramento, para espaços complexos.

A pergunta que realmente estamos tentando fazer aqui é "em que domínio a álgebra linear deve cair?"

Uma vez que um projeto é estabelecido para como operações semelhantes a matriz e tensores atuam juntas, as entidades para álgebra linear podem ser definidas apenas por casos especiais (como os apelidos que usei acima), de modo que o usuário de álgebra linear pura pode ignorar toda a hierarquia tensorial generalizada até que seja necessário (mas não terá que reescrever as coisas se e quando for). Portanto, eu não veria nenhum problema (exceto talvez inchaço) ao colocar tudo na Base.

de modo que as dimensões x, y e RGB sejam "para baixo", "para cima" e "neutras", respectivamente. As operações geométricas (por exemplo, transformações afins) poderiam então lidar com as dimensões das coordenadas da grade em forma de tensor enquanto mapeavam os valores RGB em forma de matriz. (Se mais tarde você quiser tratar os valores RGB geometricamente, terá que alterar explicitamente a máscara para esse propósito, mas eu acho que (a) é menos comum que dois tipos diferentes de operações geométricas sejam aplicados a diferentes subespaços de a mesma tabela de dados e (b) nesta situação, uma conversão explícita provavelmente melhoraria a clareza do código.)

Acho que você está misturando algo aqui. Na discussão acima, na verdade foi explicado que as coordenadas xey não carregam a interpretação do espaço vetorial, pois podem corresponder a coordenadas em uma variedade curva arbitrária, não necessariamente um espaço plano. Foi a dimensão RGB que recebeu a interpretação do vetor, embora essa também possa não ser a melhor escolha, como me lembro (não tenho um fundo decente no processamento de imagem) que o espaço de cores também é bastante curvo. Além disso, mesmo para o caso em que o domínio (x e y) forma um espaço vetorial, por que x e y seriam para cima e para baixo, ou isso era apenas um exemplo de sua notação?

De qualquer forma, também comecei com TensorToolbox.jl denotando índices covariantes e contravariantes como algum tipo de parâmetro ou máscara, mas isso logo se tornou um pesadelo completo, por isso mudei para uma representação em que cada tensor é um elemento de algum espaço vetorial , e para realizar operações, é necessário verificar se os espaços correspondem, assim como você precisa verificar se os tamanhos correspondem ao fazer operações com matrizes.

as coordenadas xey não carregam a interpretação do espaço vetorial

Desculpe, eu li demais "grade retangular" --- eu acho que @jdbates quis dizer exatamente o que ele disse. Mas não estamos apenas falando sobre a substituição de produtos escalares por produtos internos generalizados? (Perdoe-me se entendi mal, passo quase todo o meu tempo no espaço euclidiano :-)

cada tensor é um elemento de algum espaço vetorial

Parece uma boa ideia --- estou interessado em ver alguns exemplos de como funciona para o usuário (não fui muito longe lendo o código).

Tenho uma nova proposta para este problema.


(1) Fatiamento estilo APL.

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

Em particular, isso significa que "fatias singleton" - isto é, fatias onde o índice é escalar ou zero-dimensional - são sempre descartadas e M[1,:] e M[:,1] são ambos vetores, em vez de um ser um vetor enquanto a outra é uma matriz de linha, ou qualquer outra distinção semelhante.


(2) Introduzir Transpose e ConjTranspose tipos de wrapper para vetores e matrizes. Em outras palavras, algo assim:

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

e todos os métodos apropriados para fazer com que funcionem como deveriam para vetores e matrizes. Podemos querer limitá-lo a trabalhar apenas para vetores e matrizes, uma vez que não está claro o que uma transposição geral deve significar para dimensões arbitrárias (embora apenas inverter dimensões seja tentador). Quando você escreve a' você obtém ConjTranspose(a) e da mesma forma v.' produz Transpose(a) .


(3) Definir vários métodos especializados para vetores e matrizes transpostas (conjugadas), tais como:

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

etc., incluindo a substituição de todas as funções horríveis At_mul_B e análise especial com construção de transposição preguiçosa (conjugada) seguida de despacho nos tipos Transpose e ConjTranspose .


(4) Restrinja as operações de transmissão aos casos em que os argumentos são escalares ou matrizes com o mesmo número de dimensões. Assim, o seguinte, que atualmente funciona conforme mostrado, falhará:

julia> M = rand(3,4);

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

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

Em vez disso, você terá que fazer algo assim:

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

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

Acredito que esta proposta resolve todos os principais problemas que temos atualmente:

  1. comportamento de fatiamento simétrico - dimensões finais não são mais especiais.
  2. v'' === v .
  3. v' == v .
  4. v'w é o produto escalar de v e w - em particular, é um escalar, não um vetor de um elemento.
  5. v*w' é o produto externo de v e w .
  6. M*v é um vetor.
  7. M*v' é um erro.
  8. v'*M é um vetor transposto.
  9. v*M é um erro.
  10. At_mul_B operadores e análise especial desaparecem.

: +1: para tudo isso. Eu fiz alguns trabalhos em 2 e 3 em # 6837, mas nunca terminei. @simonbyrne também investigou.

+1 também. Parece que ofereceria um comportamento bastante consistente em todo o lugar.

A única parte realmente perturbadora desta proposta seria realmente que M[1,:] é um vetor vertical implícito em vez de uma matriz de linha explicitamente horizontal. Caso contrário, é na verdade um conjunto de mudanças bastante suave e sem interrupções (espera-se). A epifania principal (para mim) foi que o comportamento de fatiamento de APL poderia ser combinado com transposições preguiçosas. Se conseguirmos adesão, podemos bolar um plano e dividir o trabalho. Eu realmente espero que as transposições preguiçosas e as funções preparadas permitam alguma redução e simplificação do código.

Sim por favor! A transposição do tensor provavelmente deve permitir qualquer permutação definida pelo usuário, com a reversão das luzes como o padrão.

A transposição do tensor provavelmente deve permitir qualquer permutação definida pelo usuário, com a reversão das luzes como o padrão.

Parece que complicaria muito o tipo, talvez possamos ter um tipo PermuteDims que permite a permutação arbitrária de dimensão preguiçosa.

@Stefan : Esta parece uma boa ideia trabalhar com o vetor e 2-dim
álgebra. Apenas alguns desafios:

  1. Em relação aos casos de matriz de múltiplas dimensões: Para uma matriz A com dimensão
    (i_1, i_2, ..., i_n), se alguém quiser a transposição aplicada ao [i_2, i_3]
    dimensões - ou mesmo hash, nas dimensões [i_2, i_4]. Você pode fazer isso em
    a nova definição de transpor?
  2. Em relação à dimensão singleton: é possível que uma fatia singleton seja
    deixou intencionalmente. Julia deve manter esta dimensão singleton após o
    Cálculo? Por exemplo, se alguém definiu um vetor como uma matriz V no
    dimensão de (2,1), e deseja multiplicar a transposta com uma matriz A em
    dimensão (2,3,4). Você pode produzir o resultado de v '* A na dimensão de
    (1,3,4)?

Na quinta-feira, 16 de outubro de 2014 às 14h31, Stefan Karpinski [email protected]
escrevi:

O único disruptivo seria, na verdade, que M [1 ,:] é um vetor (vertical)
em vez de uma matriz de linha. Caso contrário, é realmente muito bom,
conjunto de mudanças não disruptivo (espera-se). A principal epifania (para mim) foi
que o comportamento de fatiamento de APL pode ser combinado com transposições preguiçosas. Se conseguirmos
adesão, podemos bolar um plano e dividir o trabalho. Eu realmente espero
que transposições preguiçosas e funções preparadas permitem alguma redução de código e
simplificação.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -59425385.

Referente a 2 e 3: depois de fazer uma tentativa bruta, cheguei à conclusão de que a transposição vetorial NÃO deve ser um subtipo de AbstractVector , caso contrário, tudo ficará bagunçado (consulte a discussão em # 6837). Acho que a maneira mais sensata de avançar é com Transpose{T,A} <: AbstractMatrix{T} e um tipo Covector separado (+ Conjugate variantes).

O outro problema significativo com que me deparei é que muitas vezes você deseja despachar um tipo de matriz específico, sua transposta ou sua transposta conjugada. Infelizmente, eu não consegui pensar em uma maneira de expressar isso por meio do mecanismo de digitação existente (veja esta discussão na lista de discussão ). Sem isso, temo que teremos muitos @eval -ing sobre 3x3 combinações possíveis de argumentos.

@simonbyrne ,

Eu apontei (em fóruns menos públicos, então provavelmente merece uma breve menção aqui) que uma alternativa potencial é lidar com todas as formas _internamente_, expandindo os tipos de índices que SubArrays podem usar. Em particular, pode-se ter um tipo de "intervalo transposto" que confere uma forma transposta ao SubArray, mesmo quando o array pai é Vector . (Consulte https://github.com/JuliaLang/julia/blob/d4cab1dd127a6e13deae5652872365653a5f4010/base/subarray.jl#L5-L9 se você não estiver familiarizado com como os SubArrays são / podem ser implementados.)

Não tenho certeza se essa estratégia alternativa torna a vida mais fácil ou mais difícil. Isso reduz o número de tipos voltados para o exterior, o que pode significar que são necessários menos métodos. (Como alguém que _ainda_ está preenchendo métodos ausentes devido à transição Color em Images , isso parece uma coisa boa.) Por outro lado, na ausência de envio triangular conveniente, poderia tornar um pouco mais complicado escrever métodos seletivos, o que pode exacerbar os problemas levantados por @simonbyrne.

Quaisquer percepções seriam bem-vindas.

Além desses detalhes, gosto do formato da proposta de

Dois pensamentos:

  • Se uma indexação como A[[2], :] tornar-se idiomática, parece um desperdício ter que criar um vetor apenas para agrupar o único índice 2 . Devemos considerar permitir A[(2,), :] pela mesma coisa ou algo semelhante? Eu acho que um único intervalo de elemento está ok, mas seria bom ter uma sintaxe para ele que seja quase tão conveniente quanto [2] .
  • Se precisarmos de um número correspondente de dimensões para fazer a transmissão, deve haver uma maneira simples de adicionar dimensões singleton a um array, talvez algo como a indexação newaxis numpy.

Eu estava pensando em propor que a indexação com ponto-e-vírgula, a la A[2;:] , poderia ser um modo de indexação diferente, onde o resultado sempre tem o mesmo número de dimensões que A - ou seja, não solte singletons e indexe com qualquer coisa com classificação superior a um é um erro. Decidi deixar isso de fora da proposta central de simplicidade, mas algo assim parece uma coisa boa de se ter.

Posso ver as preocupações expressas por @simonbyrne . No entanto, em princípio, um covetor também é apenas um vetor que vive em um espaço vetorial diferente, ou seja, o espaço dual. Portanto, tornar o tipo Transpose ou Covector não um subtipo de AbstractArray também parece um tanto desagradável. Uma solução possível, que seria uma mudança importante e provavelmente não será considerada (mas eu queria mencioná-la de qualquer maneira), é dar a toda a família AbstractArray um parâmetro de tipo extra trans , que poderia ter valores :N , :T ou :C . Para todos os métodos que assumem apenas um vetor como uma lista de números unidimensional, eles não precisariam distinguir entre os diferentes valores desse parâmetro final e, portanto, as definições dos métodos correspondentes podem permanecer como estão agora.

Para matrizes N-dimensionais com N> 2, existem várias opções. Ou transpose dá um erro e é impossível criar realmente um objeto do tipo AbstractArray{3,Float64,trans} onde trans!=:N , ou, alternativamente, :T significa apenas linha principal e transpose de uma matriz geral tem o efeito de inverter todas as dimensões. Eu acho que a última também é a convenção aceita por aquelas pessoas que usam a notação gráfica Penrose (veja http://en.wikipedia.org/wiki/Penrose_graphical_notation embora transpose não seja explicado lá, mas veja também o livro citado por Cvitanović).

Eu realmente não vejo a função de permutações de índice arbitrárias sendo suportadas por transpose , há permutedims para isso e talvez alguma abordagem preguiçosa usando SubArrays renovados. Além disso, a principal motivação para esse problema é simplificar o A_mul_B zoo, e as contrações de tensores de ordem superior não são (e não deveriam ser) suportadas pela multiplicação normal de qualquer maneira.

Tenho certeza de que há alguns novos problemas relacionados a essa abordagem que ainda não pensei.

Acho que descobri uma solução razoável para o problema de despacho aqui .

A proposta de @Jutho parece interessante e acho que vale a pena explorar. Infelizmente, a única maneira real de avaliar essas coisas é tentar implementá-las.

@toivoh ,

  • A[2:2,:] também reterá a dimensão, e isso não requer alocação ou qualquer nova sintaxe.
  • Algo como newaxis parece eminentemente viável. De fato, com a arquitetura em # 8501, parece possível criar transmissão diretamente por indexação: tenha um tipo de índice que sempre resulta em 1, não importa o valor que o usuário preencha nesse slot.

O problema com 2:2 é a repetição se houver uma expressão longa para o índice em vez de apenas 2 . Mas é claro que você sempre pode definir sua própria função para criar um intervalo de um índice.

Muito boa proposta: +1 :.

Lembre-me por que queremos v' == v ?

Nós realmente não precisamos disso, mas é legal, já que o dual de um espaço vetorial (de dimensão finita) é isomórfico a ele.

Ou mais fortemente, uma vez que as matrizes de Julia não distinguem entre índices covariantes e contravariantes, só faz sentido pensar nisso como vetores em um espaço cartesiano (métrica euclidiana = matriz de identidade = delta de kronecker), onde de fato o espaço dual é naturalmente isomórfico.

Não tenho certeza se queremos v '== v, mas acho que é muito ortogonal para
o resto. Queremos uma matriz de coluna e um vetor para comparar iguais se eles
tem elementos iguais?

Na verdade, esse é um problema diferente, pois eles têm diferentes números de dimensões.

Em particular, esta proposta remove efetivamente a identificação entre um vetor e uma matriz de coluna - porque se você fatiar uma matriz horizontalmente ou verticalmente, obterá um vetor de qualquer maneira. Anteriormente, você poderia ignorar as dimensões do singleton à direita - ou fingir que havia mais do que realmente havia. Não será mais uma boa ideia fazer isso porque um vetor pode vir de qualquer fatia de uma matriz.

Faria sentido convert algo de 1-d para 2-d adicionando uma dimensão de singleton à direita?

Com essa proposta, acho que pode não ser mais uma boa ideia. Mas talvez porque os vetores ainda se comportam como colunas, enquanto os covetores se comportam como linhas.

Uma coisa que observei em # 8416 é que sparsevec é confundido como uma matriz CSC de coluna única agora. Sparse deve ser capaz de se ajustar a isso muito bem, uma vez que um tipo de vetor esparso 1-d adequado seja implementado (o que seria o caso mais simples de um tipo Nd COO genérico, só precisa ser escrito).

Apenas absorvendo tudo isso. Então, o seguinte não funcionaria?

A [1 ,:] * A * A [:, 1] # linha de uma coluna Matrix * Matrix * de uma matriz ???

Você escreveu

v'w é o produto escalar de vew - em particular, é um escalar, não um vetor de um elemento.

Além disso, v '* w é um escalar?

Gosto da ideia do ponto (x, y) pegar quaisquer dois itens cujas formas sejam (1, ..., 1, m, 1, ..., 1) e
retornando o produto escalar, não importa o quê. Mas eu não quero que x * y forneça um ponto (x, y) neste sentido
a menos que x seja um covetor e y seja um vetor.

Não tenho certeza se essa é uma ideia tão boa, mas talvez fosse bom se
A [:, 1,1] era um vetor e A [1,:, 1] ou A [:, 1,:] eram covetores.
Parece melhor seguir ao longo de uma dimensão para o vetor - o slot, no qual você
podem contrair o tensor, sendo a álgebra linear padrão
1 (vetores linha) e 2 vetores coluna.

Em minha opinião, os dois principais desafios que abordamos anteriormente nesta edição foram:

(A) como distinguir semântica de tensor (para contrações) e semântica de matriz (para transmissão) ao operar em dados multidimensionais;
(B) como incorporar álgebra linear óbvia e conveniente dentro de uma estrutura consistente que generaliza para dimensões superiores.

Não está claro para mim como esta proposta lida com qualquer uma dessas questões. Até onde posso dizer, para alcançar (A) ainda são necessárias manipulações ad-hoc do usuário (como a funcionalidade atual); e endereçar (B) usando wrappers preguiçosos exigiria algo como as extensões SubArray sugeridas por @timholy , ponto em que se torna uma versão preguiçosa da abordagem de mascaramento discutida algum tempo atrás. Posso imaginar o fornecimento de suporte adicional para (A) usando algum mecanismo lento semelhante (como um tipo List wrapper), mas em todos esses casos, parece-me que a preguiça deve ser uma estratégia opcional.

Não sei quantos compartilham da visão de

Quando você diz, "... têm suas limitações de design a esse respeito (como discuti acima), pelo menos são compatíveis", você está falando sobre funcionalidade ausente ou algo fundamental sobre vetores e transposições que não podem ser tratadas em um nível superior, ou adicionando funções?

Alguma coisa sobre esta proposta _conflita_ com a melhoria em seus pontos (A) e (B)?

Eu realmente não vejo como as contrações de tensor são suportadas pelo operador de multiplicação padrão do matlabs *, ou por meio de qualquer outra função integrada do matlab para esse assunto. Numpy tem uma função embutida (esqueci o nome), mas também é bastante limitada, tanto quanto me lembro.

Eu também preciso das contrações tensoras em sua forma mais geral o tempo todo, é exatamente por isso que sei que especificar a contração tensorial mais geral, quanto mais implementá-la com eficiência, não é totalmente simples. É por isso que argumentei que deve haver funções especiais para isso, em vez de tentar empinar alguma funcionalidade parcialmente funcional ou, melhor, específica nos operadores padrão na base de Julia, que não cobre metade dos casos de uso. Mas fico feliz em mudar minha opinião, por exemplo, se há uma contração 'padrão' que é muito mais importante / útil do que qualquer outra? Mas isso pode ser muito dependente do domínio e, portanto, não adequado como regra geral para adoção no Julia Base.

Op 19-okt.-2014 às 22:52 heeft thomasmcoffee [email protected] het volgende geschreven:

Em minha opinião, os dois principais desafios que abordamos anteriormente nesta edição foram:

(A) como distinguir semântica de tensor (para contrações) e semântica de matriz (para transmissão) ao operar em dados multidimensionais;
(B) como incorporar álgebra linear óbvia e conveniente dentro de uma estrutura consistente que generaliza para dimensões superiores.

Não está claro para mim como esta proposta lida com qualquer uma dessas questões. Até onde posso dizer, para alcançar (A) ainda são necessárias manipulações ad-hoc do usuário (como a funcionalidade atual); e endereçar (B) usando wrappers preguiçosos exigiria algo como as extensões SubArray sugeridas por @timholy , ponto em que se torna uma versão preguiçosa da abordagem de mascaramento discutida algum tempo atrás. Posso imaginar o fornecimento de suporte adicional para (A) usando algum mecanismo lento semelhante (como um tipo de wrapper List), mas em todos esses casos, parece-me que a preguiça deve ser uma estratégia opcional.

Não sei quantos compartilham da visão de

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

aqui está uma contração sobre o último índice de A e o primeiro índice de B
meio como o ponto da mathematica

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

Sempre fui capaz de fazer contrações gerais com reformulações, permutas e talvez complexas
conjuga quando necessário mais ou menos como o anterior

não tenho certeza qual é o grande problema com os tensores, por que não podemos simplesmente implementar alguns desses
funções?

Sim, exatamente. Nesta questão, tudo o que queremos resolver para seguir em frente é

  1. Quais dimensões diminuir na indexação? O "estilo APL" parece incontroverso.
  2. O que vector' dá?

Para contrações de tensores, com tipos apropriados e funções preparadas, acho que poderíamos realmente obter implementações de alto desempenho.

Minha sensação é que os tensores vão cuidar de si mesmos e temos que ter certeza
que os usuários de álgebra linear não fiquem frustrados.

Minha maior preocupação é que

(pegar uma linha de um array 2d) * (array 2d) * (pegar uma coluna de um array 2d)

que é uma operação comum ainda não funcionará a menos que tomemos
(faça uma fila) com covector ou talvez melhor ainda
marque-o com um índice geral de slots.

@JeffBezanson , quando digo que essas operações são suportadas, quero dizer que os tipos de dados e funções integrados são projetados especificamente com eles em mente, por exemplo, como a função Dot do Mathematica. Assim, para o usuário, existe uma maneira embutida, documentada e / ou óbvia de fazer certas coisas. Para qualquer design, é possível obter suporte para qualquer coisa adicionando funções, assim como na implementação atual; então não é uma questão de conflito técnico, é uma questão de design.

@Jutho , eu não uso muito o MATLAB, então não posso comentar. Concordo que o design do NumPy é menos coerente do que o do Mathematica (como discutido acima), mas também suporta uma gama mais rica de comportamentos. Eu concordo que a álgebra linear básica deve deixar o mecanismo tensor geral invisível para usuários que não precisam dele, mas devido aos excelentes recursos de linguagem de Julia, não parece necessário introduzir implementações divergentes para eles, como o NumPy e o Mathematica têm foi forçado a fazer em certa medida. Pareceu-me que essa questão era, pelo menos em parte, sobre encontrar o sistema unificado certo para ambos, para revelar quais especializações deveriam ser usadas para os casos comuns de álgebra linear: por exemplo, o que fazer com vector' .

A [1 ,:] * A * A [:, 1] # linha de uma coluna Matrix * Matrix * de uma matriz ???

Correto - você teria que escrever A[1,:]' * A * A[:,1] .

Além disso, v '* w é um escalar?

Sim, v'w são a mesma coisa. Uma das coisas boas dessa proposta é que ela elimina completamente os hacks sintáticos baratos.

Não tenho certeza se essa é uma ideia tão boa ...

Eu não acho que seja. Um dos objetivos desta proposta é tornar as regras de fatiamento e indexação simétricas e isso tornaria um dos índices especial, o que me parece frustrar todo o propósito. Se o fatiamento vai ser assimétrico, podemos também manter o comportamento atual.

@thomasmcoffee Você apenas terá que ser mais específico. Claro que todo mundo quer que as coisas sejam coerentes, documentadas, óbvias etc. A questão é: a proposta na mesa atende a esses objetivos? Talvez a proposta atual não afete de forma alguma essas metas, o que está ok - então, desde que leve a uma melhoria em outro lugar, ainda temos uma melhoria líquida.

Então deixe-me ver se entendi

Se A não for quadrado

| | Atual | Proposto | MATLAB |
| --- | --- | --- | --- |
| A * A [1 ,:] | Não Sim | Não
| A * A [1 ,:] '| Sim | Não Sim |
| A [:, 1] A | Não
A [:, 1] ' A | Sim | Sim | Sim |

e se A é quadrado

| | Atual | Proposto | MATLAB |
| --- | --- | --- | --- |
| A * A [:, 1] | Sim | Sim | Sim |
| A * A [:, 1] '| Não Não Não
| A [1 ,:] A | Não
A [1 ,:] ' A | Não Sim | Não

Juro que acabei de postar uma resposta para isso, mas de alguma forma ela desapareceu no éter. Tudo isso está correto. No arranjo atual, você precisa considerar se você está pegando uma fatia de linha ou uma fatia de coluna, bem como se você está multiplicando à esquerda ou à direita ao decidir se transpõe ou não (colunas são transpostas à esquerda, linhas são transposta à direita). Na proposta, você considera apenas de que lado está multiplicando - você sempre transpõe para a esquerda, nunca para a direita.

Estaria tudo bem se

dot(x,y) e dot(x.',y) e dot(x,y.') e dot(x.',y.') dão o mesmo escalar?

ou seja, Σᵢ conj (xᵢ) * yᵢ

desta forma, pode-se fazer dot (x, A * y) sem pensar muito

Os exemplos de @alanedelman parecem um pouco retrógrados em lugares que você não deveria, devido à indexação APL. Talvez isso seja motivação suficiente para ter uma variante da indexação que preserva a dimensão, como eu acho que foi discutido (por exemplo, A[i; :] ?)

Mas então você gostaria de dar um covector no caso acima.

@alanedelman , não vejo nenhuma razão para esses métodos de dot não existirem e darem o mesmo resultado.

Sempre fui capaz de fazer contrações gerais com reformulações, permutas e talvez complexas
conjuga quando necessário mais ou menos como o anterior

É exatamente assim que é implementado na função tensorcontract em TensorOperations.jl, se você escolher o método: BLAS, que certamente é o mais rápido para tensores grandes. Eu também escrevi uma implementação Julia nativa, usando a funcionalidade Cartesian.jl (e espero que um dia usando funções preparadas) que elimina a necessidade de permutados (alocação de memória extra) e é mais rápida para tensores menores.

Eu estava respondendo à falsa alegação de que o matlab fornece funcionalidade embutida para isso, o que Julia não oferece. Tanto o reshape quanto o permutedims estão disponíveis em Julia. Numpy de fato tem a função tensordot que faz exatamente isso, mas não permite que você especifique a ordem do índice do tensor de saída, então você ainda precisa de um permutedims depois se você tiver uma ordem de saída específica em mente.

Mas isso está indo muito longe do tópico atual, que é de fato obter um comportamento consistente para álgebra vetorial e matricial.

1 para a proposta de Stefan. Parece fornecer uma semântica extremamente clara, mas suficientemente flexível. Como um usuário de álgebra linear, mesmo alguém acostumado com a sintaxe do estilo MATLAB, eu consideraria as regras simples o suficiente de usar.

Estou um pouco confuso sobre o que ' significa em geral. Se v é um vetor, v' é um transpose . Se a é um array 2d, a' seria um transpose . Ambos parecem ser definidos no interesse de poderem facilmente formar b' * a , contraindo a primeira dimensão de b com a primeira dimensão de a .

Parece não haver consenso sobre uma definição de a' quando a dimensão de a é> 2. Eu não ouvi ninguém propor nada além de inverter as dimensões, e isso coincide com b' * a contraindo a primeira dimensão de b com a primeira dimensão de a .

Acho que seria bom se pudéssemos afirmar o que ' faz sucintamente, sem nos referirmos ao tamanho da coisa em que está operando.

Parece razoável ter outra função de contração disponível no Base para situações mais gerais, por exemplo, contract(a, b, 2, 3) para contrair a 2ª dimensão de a com a 3ª de b .

A propósito, dot(a,b) == a'*b quando a e b são vetores, mas dot(a,b) está atualmente indefinido para matrizes. Podemos ter dot(a,b) = trace(a'*b) ?

@madeleineudell : Estou um pouco confuso sobre o que 'deve significar em geral.

Eu compartilho essa preocupação. Você pode basicamente considerar as propriedades 2-5, 7, 8 e 10 em minha proposta como características definidoras. Ou seja, você quer que isso segure:

  • v' é unidimensional, mas não é um vetor
  • v'' === v
  • v' == v
  • v'w é um escalar
  • v*w' é uma matriz
  • v'*M é o mesmo tipo de coisa que v'
  • M' é bidimensional, mas não uma matriz, talvez uma visualização de matriz

Em particular, não há restrições sobre o que significa ou faz para matrizes de dimensões superiores. Uma teoria geral do que são covetores que englobem dimensionalidades mais altas seria boa, mas não estou convencido de que possa realmente ser feita sem complicar muito as coisas, por exemplo, tendo dimensões para cima / para baixo ou tendo cada dimensão rotulada por um índice.

Está claro, pelo menos, que a regra geral para ' não é inverter as dimensões, já que não é o que faria com vetores e covetores.

A única regra simples que posso pensar que captura o comportamento acima para ambos os vetores, covetores e matrizes é permutar as primeiras duas dimensões (um vetor tem uma segunda dimensão ausente e um covetor tem uma primeira ausente e uma segunda presente).

Em 20 de outubro de 2014, às 09h05, toivoh [email protected] escreveu:

Está claro, pelo menos, que a regra geral para 'não é inverter as dimensões, visto que não é o que faria com vetores e covetores.

A única regra simples que posso pensar que captura o comportamento acima para ambos os vetores, covetores e matrizes é permutar as primeiras duas dimensões (um vetor tem uma segunda dimensão ausente e um covetor tem uma primeira ausente e uma segunda presente).

Eu diria que isso não é verdade se você quiser pensar sobre tensores gerais. Pode ser verdade se você pensar em matrizes e vetores como alguns blocos com números e pensar em v como uma coluna ev 'como uma linha.

No entanto, se você pensar em v como algum elemento de um espaço vetorial e w = v 'como alguma forma de mapear v para um elemento w do espaço dual V_, então w ainda é um vetor, ou seja, um objeto unidimensional. Em geral, é necessária uma métrica para definir esse mapeamento de V a V_, ou seja, w_i = g_ {i, j} v ^ j. Agora, se V é um espaço cartesiano, ou seja, R ^ n com a métrica euclidiana padrão, então V * é naturalmente isomórfico a V. Isso significa que não há noção de índices superiores ou inferiores (covariante ou contravariante) e, portanto, w_i = v_i = v ^ i = w ^ i. Eu diria que este é o caso usado na maior parte da programação, ou seja, caso que precisa ser suportado por Julia Base. (Para um espaço vetorial complexo, V * é naturalmente isomórfico a Vbar, o espaço vetorial conjugado e, portanto, o mapeamento natural é w_i = conj (v ^ i).)

A convenção para denotar vetores como colunas com números, vetores duais como linhas com números e, portanto, matrizes (que são elementos de V \ otimes V_) como blocos com números é extremamente conveniente, mas para assim que você também quiser considerar dimensões mais altas arrays, ou seja, elementos de espaços de produto tensor de ordem superior. Nesse caso, um 'vetor linha', ou seja, objeto bidimensional onde a primeira dimensão tem tamanho 1, para dizer na terminologia matlab / julia, é algum elemento de um espaço de produto tensorial V1 \ otimes V2, onde V1 apenas acontece a ser R (ou algum outro espaço unidimensional). Eu concordo que isso pode não ser o que você deseja como comportamento padrão, mas do que eu preferiria que não se tentasse definir transpor para um array geral e se referir a permutedims, como o matlab faz. Reverter as duas primeiras dimensões de um tensor geral como convenção padrão não faz sentido. Transpondo um elemento de algum espaço de produto tensorial de ordem superior V1 \ otimes V2 \ otimes ... \ otimes Vn não tem uma definição única. A convenção para inverter todas as dimensões decorre apenas de uma representação gráfica conveniente de Penrose, conforme referido acima. Além disso, é aquele que mapeia o espaço da matriz (V \ otimes V_) para si mesmo (V * \ otimes V * = V \ otimes V ).

Posso ver dois caminhos a seguir:
1) Faça o operador de conveniência '(e talvez até *) trabalhar com matrizes arbitrárias de ordem superior usando alguma convenção escolhida, dentro da configuração de tensores cartesianos (ou seja, nenhuma distinção entre índices superiores ou inferiores). Isso pode trazer alguns resultados surpreendentes para casos de uso típicos.

2) Restringir 'e * a vetores e matrizes. Erro em matrizes de ordem superior. Acho que esta é a abordagem mais popular (por exemplo, Matlab etc).

Este post é um pouco o outro lado da posição que assumi no ano passado, para

explore a ambivalência de ambos os lados.

Espero que esteja tudo bem.
Finalmente percebi que há uma lógica para a abordagem atual, mas ela nunca foi articulada.
Parecia tanto com um hack que me irritou. De alguma forma agora que eu entendo
, estou gostando mais.

Na abordagem atual, todos os arrays têm dimensões infinitas com 1s descartados implícitos.
O operador de apóstrofo 'poderia significar o intercâmbio das duas primeiras dimensões,
mas na verdade, como existe hoje, significa

ndim (A) <= 2? interchange_first_two_dims: no_op

desculpe se todo mundo viu isso e eu simplesmente perdi. Minha mente estava atolada
com a ideia de que os vetores eram unidimensionais, não infinitos e
portanto, uma transposta deve inverter as dimensões, portanto, um no_op.

Eu estou bem com o apóstrofo fazendo isso, ou o apóstrofo sempre trocando
as duas primeiras dimensões - não acho que me importe. Eu acho que o apóstrofo existe
para álgebra linear e não álgebra multilinear.

Eu brinquei se apóstrofo-estrela ("'*") (se possível! Ou outra coisa se impossível)
deve significar contrato da última dimensão para a primeira dimensão (como o ponto do Mathematica)
e tem indexação apl e nenhum covectors. Parte do meu cérebro ainda acha que vale a pena explorar,
mas, à medida que acordo, essa abordagem parece cada vez melhor.
(Vamos ver como me sinto mais tarde hoje)

Não me arrependo de ter iniciado este tópico no ano passado, mas estou me perguntando novamente em quais casos
realmente irrita as pessoas hoje ... podemos obter uma lista de exemplos? ... e se
esclarecer esses casos é melhor do que mudar o que fazemos.

Eu li todo este tópico e vi muitos princípios - o que é bom,
mas não casos de uso suficientes para pensar nas coisas.

Posso dizer que muitas vezes fico incomodado com

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

principalmente porque não consigo me lembrar disso.

Apenas um experimento mental. Que funcionalidade seria perdida se redefiníssemos * para servir como um operador de contração semelhante ao que @alanedelman tem em mente para '* ? Isso não eliminaria a necessidade de ' em operações algébricas lineares (além de funcionar como uma transposição para matrizes)? Só posso ver que isso nos privaria do produto externo w*v' , que poderia ser facilmente substituído por outer(w,v) .

EDIT: Supondo que seja o fatiamento de APL. Por exemplo A[1,:]*A*A[:,1] teria o resultado esperado sem a necessidade de transpor o primeiro operando com ' .

Eu também pensei nisso. Eu acho que v * w sendo um produto escalar parece apenas uma sobrecarga de esteróides
que pode estar sujeito a erros.
Este é um lugar onde o ponto de mathematica não parece tão ruim.

Então, para resumir, um contrato do último ao primeiro parece razoável, mas
se poderia ser estrela apóstrofe ou poderia ser * ou deveria ser ponto
tem problemas.

Algo não relacionado, mas não completamente desvinculado ...
Gostaria de salientar que o ponto-estrela que todos lêem originalmente como ponto-estrela (ouvi dizer) era
pretendia ser uma estrela porque o operador POINTWISE era o que
foi feito

Posso dizer que muitas vezes fico incomodado com

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

principalmente porque não consigo me lembrar disso.

Eu sempre pensei "por que não usamos apenas , ?"

Qual funcionalidade seria perdida se redefiníssemos * para servir como um operador de contração semelhante ao que @alanedelman tem em mente para '*?

Perderíamos a associatividade: por exemplo, (M*v)*v daria o dot(v,M*v) atual (um escalar), enquanto M*(v*v) daria M.*dot(v,v) (uma matriz).

Por que simplesmente não tornamos dot o operador de contração (que não é associativo de qualquer maneira)? Também poderíamos definir contrações de ordem superior, por exemplo, ddot(A::Matrix,B::Matrix) == A⋅⋅B == trace(A'*B) .

Então, eu entendi corretamente que o único propósito de introduzir transposições vetoriais é nos salvar da não associatividade de * ? A ambigüidade do exemplo de @alanedelman poderia ser corrigida pela transposição de um dos vetores ( M*v*v' vs M*v'*v ) mas o mesmo pode ser feito com parênteses ( M*(v*v) vs (M*v)*v ) sem todo o incômodo de implementar a transposta para vetores (como @Jutho já apontou implementar transposta para tensores de ordem superior não faz sentido matematicamente de qualquer maneira). Para mim, a questão é qual notação é mais legível / concisa / elegante / pura.

Na verdade, M*(v*v) e (M*v)*v são atualmente analisados ​​como

*(M, v, v)

para permitir um produto de matriz que otimiza a ordem de multiplicação com base nos tamanhos dos argumentos, portanto, o analisador também teria que ser alterado.

Mas, pelo menos pessoalmente, acho que associatividade é um grande negócio. Você tem que traduzir entre matemática e a maioria das linguagens de programação; para Julia, ainda espero que você não tenha que traduzir muito.

(como @Jutho já apontou implementar transpor para tensores de ordem superior não faz sentido matematicamente de qualquer maneira).

Não acho que seja exatamente o que eu disse.

Qual funcionalidade seria perdida se redefiníssemos * para servir como um operador de contração semelhante ao que @alanedelman tem em mente para '*?

Perderíamos a associatividade: eg (M_v) _v daria o ponto atual (v, M_v) (um escalar), enquanto M_ (v_v) daria M._dot (v, v) (uma matriz).

Por que simplesmente não tornamos ponto o operador de contração (que não é associativo de qualquer maneira)? Também poderíamos definir contrações de ordem superior, por exemplo, ddot (A :: Matrix, B :: Matrix) == A⋅⋅B == trace (A '* B).

Com essa linha de raciocínio, simplesmente não precisamos mais do operador de multiplicação para vetores e matrizes, podemos apenas escrever tudo em termos de ponto:
A ∙ B
A ∙ v
v ∙ A ∙ w
v ∙ w

Então essa é apenas a proposta @pwl , mas com * substituído por ponto.

Mas, pelo menos pessoalmente, acho que associatividade é um grande negócio. Você tem que traduzir entre matemática e a maioria das linguagens de programação; para Julia, ainda espero que você não tenha que traduzir muito.

Acho que parte do problema é que mesmo na matemática existem convenções diferentes. Está pensando em termos de matemática de vetores de linha e coluna, ou queremos um comportamento mais abstrato em termos de operadores lineares e espaços duais (onde eu acho que um operador não é multiplicado por um vetor, mas aplicado a um vetor, então por que não escrever A (v) em vez de A * v ou A ∙ v)?

Não estou precisando muito de uma sintaxe de conveniência, pessoalmente prefiro escrever ponto (v, w) em vez de v '* w todas as vezes, uma vez que o primeiro expressa mais claramente a operação matemática independente de base (Na verdade, primeiro é necessário definir um produto escalar antes que um mapeamento natural de vetores para vetores duais possa ser definido). =

Desculpe por ser tão ignorante, mas por que exatamente M[1, :] ser uma transposição, se comportando como v' ?

Vou adicionar à minha lista de coisas que me incomodam

1

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

2
v=rand(3)
v' * v atualmente tem ndims == 1 (a proposta de @StefanKarpinski corrige isso)
(v' * v)/( v' * v ) has ndims ==2 (isso realmente me incomoda e também seria corrigido)

Ainda assim, eu realmente não quero covetores
e indexação de apl é algo que eu continuo indo e voltando
--- ainda pensando, mas adoraria ver a lista de coisas que
incomode outras pessoas na atual julia

Eu definitivamente gosto da ideia do cdotcontratação do último índice para o primeiro índice, além do operador *
e permitir o ponto (a, b, ..) permite contrações muito gerais.

Apesar da convenção de nomenclatura do Numpy (e talvez da aparência do meu post anterior), tenho sentimentos um tanto confusos sobre o uso de ponto para contrações tensoras gerais. Esta é a primeira frase da Wikipedia:

Em matemática, o produto escalar, ou produto escalar (ou às vezes produto interno no contexto do espaço euclidiano), é uma operação algébrica que pega duas sequências de números iguais (geralmente vetores de coordenadas) e retorna um único número.

Em minha mente, também, ponto deve retornar um escalar.

@ brk00 , o problema com sua pergunta é que não está claro como estender isso a fatias de matrizes de dimensão superior / classificação superior (eu realmente não gosto do dimensional mundial para isso).

Desculpe por ser tão ignorante, mas por que exatamente M [1,:] não pode ser uma transposta, comportando-se como v '?

Pode, mas como generalizar isso?

@Jutho , desculpe, eu deveria ter formulado de forma diferente. Eu quis dizer sua linha "Transpor um elemento de algum espaço de produto tensorial de ordem superior [...] não tem uma definição única", então não há nenhuma motivação matemática para introduzir uma definição particular, ou para defini-la.

@alanedelman :

Vou adicionar à minha lista de coisas que me incomodam

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

O comportamento de concatenação não está relacionado a esse problema. Não vamos turvar ainda mais as águas.

Eu definitivamente gosto da ideia de `cdot contrair o último índice para o primeiro índice, além do operador *
e permitir o ponto (a, b, ..) permite contrações muito gerais.

Isso também é tangencial. Vamos nos concentrar em matrizes e fatias.


Na abordagem atual, todos os arrays têm dimensões infinitas com 1s descartados implícitos.

Isto não é realmente verdade. Se isso fosse verdade, não haveria distinção entre ones(n) , ones(n,1) , ones(n,1,1) , etc. Mas esses são todos objetos diferentes com tipos diferentes que não são iguais a cada um de outros. Fazemos algum esforço para implementar as coisas de forma que se comportem _similarmente_, mas isso está muito longe de os arrays terem dimensões infinitas.


A lista de coisas que atualmente são incômodas, em grande parte, espelha (um subconjunto das) boas propriedades da minha proposta acima. Nomeadamente:

  1. comportamento de fatiamento assimétrico - as dimensões finais são especiais.
  2. v'' !== v - na verdade v'' != v ; isso ocorre porque eles não têm a mesma classificação.
  3. v' != v - mesma oferta.
  4. v'w é um vetor de um elemento, não um escalar.
  5. precisamos de análise especial para A*_mul_B* , que é a coisa mais terrível de todas.

Eu concordo com a maioria dos pontos de v' == v : Eu não acho que eles deveriam ser iguais. Sim, eles podem ser isomórficos, mas ainda são objetos diferentes, no sentido de que se comportam de maneira muito diferente: as matrizes M e M' também são isomórficas, mas acho que nunca quereríamos que fossem iguais (a menos que sejam hermitianos, é claro).

Minha visão geral com os tipos Covector era que eles deveriam ser relativamente leves: além das operações básicas (multiplicação, adição, etc.), evitaríamos definir muitas operações (eu até hesitaria em definir indexação ) Se você quiser fazer algo com ele, deve convertê-lo novamente em um vetor e fazer suas coisas lá.

+1 para a opinião de opinião de

Também concordo com @simonbyrne.

Sim, v e v' têm tipos diferentes, mas já consideramos diferentes tipos de array com a mesma forma e dados iguais - por exemplo speye(5) == eye(5) . Por que covector é diferente de esparso?

Espero que os covetores transmitam como matrizes de linha. Para as matrizes A e B que são consideradas iguais, até agora mantém que

all(A .== B)

o que não seria o caso se um fosse um vetor e o outro fosse um covetor.

Para mim, um covector é mais como uma matriz de linha do que uma matriz de vetor ou coluna.

Eu acho que uma questão chave é o que é size(v' )? Se a resposta for (length(v),) então acho que v == v' deve ser verdade. Se size(v') == (1,length(v)) então provavelmente deveria ser falso, mas indiscutivelmente então v' == reshape(v,1,length(v)) deveria ser verdadeiro. Portanto, a questão é: v' ser um tipo especial de vetor ou um tipo especial de matriz?

Cada vez mais estou convencido de que esta questão é realmente sobre o que transpor realmente significa.

Covectors não são realmente matrizes, então não acho que possamos realmente dizer que "forma" eles têm. Um covector é realmente definido pelo que faz, isto é, *(::Covector, ::Vector) dá um escalar: como qualquer AbstractVector não faz isso, não é realmente a mesma coisa.

Outro problema estaria no campo complexo: teríamos v' == v ou v.' == v ?

@simonbyrne :

Um covector é realmente definido pelo que faz, isto é, *(::Covector, ::Vector) dá um escalar: como qualquer AbstractVector não faz isso, não é realmente a mesma coisa.

Este é um ponto muito bom. No entanto, pode ser bastante frustrante se v' não puder ser usado como um objeto. Talvez a abordagem certa seja tratar v' como uma matriz de linha engraçada em vez de um vetor engraçado.

Talvez a abordagem certa seja tratar v 'como uma matriz de linha engraçada em vez de um vetor engraçado.

Mais ou menos, mas não acho que deva ser um subtipo de AbstractMatrix : acho que AbstractCovector deve ser um tipo de nível superior. Eu ficaria feliz em definir length(::Covector) , mas não acho que devemos definir um método size .

Não tenho certeza sobre como lidar com a transmissão: a menos que possamos chegar a critérios razoáveis, eu erraria ao não definir métodos de transmissão.

Esta discussão parece estar convergindo para o uso de transposes e vetores como são usados ​​na engenharia, ou seja, pense em tudo como sendo matrizes. Pense nos vetores como uma coluna e nos convetores como uma linha. Isso é bom para vetores e mapas lineares no espaço cartesiano (o caso de uso típico), mas começa a falhar se alguém tentar generalizar para álgebra multilinear ou para espaços vetoriais mais gerais, etc. Parece haver muitas confusões originadas do fato de que para espaços cartesianos muitas coisas são equivalentes que não são equivalentes para espaços gerais. Não sou necessariamente contra isso como o comportamento padrão de Julia, mas realmente discordo de algumas das afirmações acima, como se elas fossem “matematicamente corretas”. Então, deixe-me contradizer os abaixo.

Em 20 de outubro de 2014, às 17:39, Simon Byrne [email protected] escreveu:

Eu concordo com a maioria dos pontos de

Esta afirmação não faz sentido em nenhum nível.

1), acho que você está abusando do significado de isomórfico aqui. Isomorphic é uma relação entre dois espaços (neste cenário). Em geral, para cada espaço vetorial (real) V existe um espaço dual V * de funções lineares (para espaços complexos, existe também o espaço conjugado e o dual do espaço conjugado). Estes são espaços tipicamente diferentes, e não há nem mesmo um mapeamento único ou natural de um para o outro, ou seja, em geral não há significado para associar os elementos v de V aos elementos phi de V *. A única operação geral é aplicar a função linear, ou seja, phi (v) é um escalar.

Um mapeamento natural de V para V * pode ser definido uma vez que você tenha uma forma bilinear V x V -> escalares, tipicamente um produto / métrico interno. Pode-se então definir phi_i = g_ {i, j} v ^ j.

2), não existe de facto nenhuma operação natural associada à "transposição de um vector" (ver ponto 3). Essa confusão vem da identificação de vetores com matrizes de coluna.
Colocando provavelmente muito forte, eu realmente gostaria de ver algum caso de uso da transposição de um vetor que você não pode obter usando ponto e talvez algum produto externo / operação de produto tensor?

No entanto, se você quiser usar a transposição como uma forma de mapear vetores para vetores duais, ou seja, mapear v em V para algum phi = v 'em V_, então você está assumindo que está trabalhando com o produto interno euclidiano padrão (g_ { i, j} = delta_ {i, j}). Nesse ponto, você está erradicando a distinção entre índices covariantes e contravariantes, está essencialmente trabalhando com tensores cartesianos e V e V_ tornam-se naturalmente isomórficos. Como afirmado acima, w_i = v ^ i = v_i = w ^ i, então sim, v == w e eu diria mesmo que não há nada que os distinga.

3) A operação “transpor” é originalmente definida apenas para mapas lineares de V -> W (http://en.wikipedia.org/wiki/Dual_space#Transpose_of_a_linear_map), e na verdade mesmo aí pode não significar o que você pensa. A transposta de um mapa linear A: V-> W é um mapa A ^ T de W _-> V_, ou seja, ele atua sobre vetores em W_, em vetores duais, e produz elementos de V_, ou seja, covetores de V. Isso significa, se você pensar nisso em termos da transposta usual A ^ T de uma matriz A, que esta matriz A ^ T deve ser multiplicada com colunas representando vetores em W * e que a coluna que sai disso representa um covetor de V Portanto, neste ponto, a identificação de vetores duais com vetores linha já falha.

No entanto, no caso de uso típico de espaços vetoriais reais onde V * e W * são identificados com V e W por meio do produto interno euclidiano padrão, a transposição de um mapa linear pode ser identificada com o adjunto desse mapa linear, que é de fato um mapa de V-> W, como a maioria das pessoas pensa, também é o caso para a transposição do mapa. No caso complexo, isso falha uma vez que a transposição de matriz comum (sem conjugação complexa) só faz sentido como um mapa W _-> V_, como um mapa W-> V não é uma definição independente de base e, portanto, não é operacionalmente significativo.

Quanto ao que transpor deve significar para arrays de dimensões superiores, dentro da abordagem matlab / engenharia para a qual estamos convergindo: deve ser um erro, não generaliza. Isso não significa que não haja como defini-lo. O problema é o que representa uma matriz de ordem superior. É um elemento de um espaço de produto tensorial V1 \ otimes V2 \ otimes ... \ otimes VN, é um mapa multilinear agindo em V1 x V2 x ... x VN, é um mapa linear de algum espaço de produto tensorial V1 \ otimes V2 \ otimes … \ Otimes VN para outro espaço de produto tensorial W1 \ otimes W2 \ otimes… \ otimes WM? Para o caso bidimensional, há uma resolução. Identificando mapas lineares A: V-> W com vetores em W \ otimes V_, a transposição no sentido do mapa linear corresponde a inverter os espaços neste espaço de produto tensorial, A ^ i_j -> A_j ^ i com A ^ T em V_ \ otimes W = V * \ otimes W _, que é de fato um mapa de W_ -> V. O bom de inverter dimensões para objetos de ordem superior é que ele tem uma representação gráfica conveniente.

Em conclusão, acho que o problema é se o vetor de Julia precisa capturar as propriedades de um vetor geral arbitrário no sentido matemático, ou se ele apenas representa uma coluna de números, uma lista, ... As palavras vetor, tensor, ... têm bem - significados operacionais e independentes de base definidos em matemática, que podem entrar em conflito com o tipo de operações que você deseja definir em um vetor Julia. Ao contrário, alguns objetos são realmente vetores do ponto de vista matemático (vetores duais etc.) que provavelmente não deveriam ser identificados com vetores Julia, uma vez que o tipo de vetor Julia (abstrato) padrão não tem como distinguir entre diferentes espaços vetoriais.

Nesse aspecto, há certa assimetria, já que o termo Matrix é muito menos sobrecarregado, mesmo do ponto de vista matemático uma matriz é apenas uma representação conveniente de um mapa linear em uma determinada base. Observe que também há muitos casos em que você não quer representar um mapa linear como uma matriz, mas sim como uma função (esse problema já foi levantado, por exemplo, nos argumentos de eigs etc.). Nesse aspecto, posso ver porque o matlab nem se preocupou em ter um vetor, uma estrutura verdadeiramente unidimensional. Nessa abordagem, tudo é interpretado apenas como uma 'matriz', ou seja, um bloco com números, de qualquer maneira.

Questão? Seja c um covector. O que é fft (c)?

Resposta: fft (c ')' que leva conjugados complexos de maneiras potencialmente inesperadas
quando comparado com fft (c)

os usuários podem se beneficiar por ser indefinido
(ou talvez isso seja bom para os usuários, se bem documentado ??)

Aposto que há muito mais desse tipo de coisas

Suspeito que a coisa certa a fazer em Base por enquanto é apenas definir:

  • covector vezes outro vetor
  • covector vezes uma matriz
  • covector 'para obter um vetor

+1 para suporte mínimo para covectors por enquanto.

Sim, +1.

Então, se eu mencionei isso, tenho certeza que
norma (covetor, q) deve ser norma (vetor, p) onde 1 / p + 1 / q = 1
da desigualdade de Holder, que demoraria muito para ser implementada. :-)

Graças a Deus por p = q = 2

Isso não seria tão difícil de implementar:

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

Você provavelmente vai querer alguma verificação adicional para evitar q == 0 e q == 1 .

Eu acho q == 1 ficaria bem

Med venlig hilsen

Andreas Noack

22/10/2014 15:19 GMT-04: 00 Stefan Karpinski [email protected] :

Isso não seria tão difícil de implementar:

norma (c :: Covector, q :: Inteiro) = norma (c.vetor, q / (1-q))

Você provavelmente deseja verificações adicionais para evitar q == 0 e q == 1.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -60139762.

Estou trabalhando em uma redação da proposta do covector (que eu apoio principalmente), mas pode ser útil postar agora um ponto que torna precisa a noção de @StefanKarpinski de "matriz de linha engraçada".

Covetores não são vetores, mas a descrição do porquê nem sempre é clara. Um covetor (ou vetor sutiã, como os físicos o chamam) é um funcional linear que come um vetor e cospe um número que é um produto escalar.

Mais precisamente:

  • Sejam V = V(F) e W = W(F) vetores sobre algum campo de elementos F ,
  • v e w sejam vetores que são elementos de V e W respectivamente,
  • <.,.> seja um produto interno tal que <.,.> : V × W → F e v, w ↦ <v, w>

O covetor correspondente a v é o funcional linear v' : W → F que realiza o mapeamento w ↦ <v, w> .

A descrição usual termina dizendo que v' é um elemento de um espaço dual V* sem muito mais dizer sobre o que o espaço dual _é_.

Para o pessoal da ciência da computação, há uma descrição simples de v' como um currying do produto interno em relação ao seu primeiro parâmetro e V* como a coleção de funções que são de um parâmetro curries com diferentes primeiros vetores.

Este artigo da Wikipedia pode ser útil para reconciliar as diferentes notações associadas às duas descrições, mas elas expressam exatamente o mesmo conceito.

Além disso, inicialmente pensei que a indexação em um covector não deveria ser permitida. No entanto, indexar v'[1] é equivalente a aplicar v' ao vetor de base canônica e₁ = (1, 0, ...) , de forma que v'[1] possa ser definido como <v, e₁> . Semânticas de indexação mais gerais, como 1:n seguem naturalmente da aplicação de v' a uma matriz de projeção adequada, com cada coluna sendo um vetor de base canônica.

Considerando a semântica de indexação de covetores no contexto de # 987 dá outra razão porque os covetores não são AbstractVector s: não é possível em geral indexar v' barato porque tudo depende de quão caras as quantidades como <v, e₁> devem ser computados. Em geral, você poderia ter um produto interno definido em relação a uma matriz <v, w> = v'*A*w e o custo de indexação é dominado pelo produto matvec A*w (ou A'*v ), que certamente é muito caro para se qualificar como AbstractVector .

Já que estamos falando sobre DFTs e normas ... isso passou pela minha cabeça hoje

se f é uma função de um vetor e produz um escalar, então eu gostaria que diff(f) fosse uma função que aceita um Vector e produz um Covector de modo que f(x) ~= f(x0) + diff(f)(x0) * (x-x0) . Um uso muito comum do gradiente é obter uma aproximação linear da mudança incremental na saída da função dada uma mudança incremental na entrada. Se a entrada for um vetor, então parece natural que o gradiente seja algo que se contrai ao longo de todas as dimensões da entrada.

Mas se eu fosse implementar a descida de gradiente, precisaria adicionar (uma versão em escala de) o gradiente em x a ele mesmo. Nesse sentido, o gradiente é um vetor que aponta na direção mais acentuada, cuja norma é proporcional à taxa de mudança ao longo dessa direção mais acentuada.

Meu instinto diz que "o gradiente da função de entrada do vetor em um ponto é um covetor" é mais importante.

Eu suspeito que a coisa certa a fazer no Base por enquanto é apenas definir:

covector vezes outro vetor
covector vezes uma matriz
covector 'para obter um vetor

Apesar da impressão que você pode ter obtido de minhas mensagens terríveis anteriores, marque com +1.

No entanto, algumas observações sobre isso:

Estou trabalhando em uma redação da proposta do covector (que eu apoio principalmente), mas pode ser útil postar agora um ponto que torna precisa a noção de @StefanKarpinski de "matriz de linha engraçada".

Covetores não são vetores, mas a descrição do porquê nem sempre é clara. Um covetor (ou vetor sutiã, como os físicos o chamam) é um funcional linear que come um vetor e cospe um número que é um produto escalar.

Acho que vetores / covetores duais (também prefiro o termo funcional linear) podem ser definidos sem um produto interno. Você só precisa de um produto interno se quiser definir um mapeamento de V a V *

Mais precisamente:

Seja V = V (F) e W = W (F) vetor sobre algum campo de elementos F,
v e w são vetores que são elementos de V e W respectivamente,
<.,.> seja um produto interno tal que <.,.>: V × W → F e v, w ↦
É muito estranho, e acho impossível, ter um produto interno definido entre dois espaços vetoriais diferentes V e W. Como você define a propriedade de definição positiva?
O covetor correspondente av é o funcional linear v ': W → F que realiza o mapeamento w ↦.

A descrição usual termina dizendo que v 'é um elemento de um espaço dual V *, sem muito mais dizer sobre o que é o espaço dual.

Acho que é bastante claro, certamente para o caso de dimensão finita, que o espaço dual, por seu próprio nome, é um espaço vetorial. No entanto, o resumo de meu discurso anterior é que o tipo de vetor (abstrato) de Julia é mais parecido com uma lista. Pode ser usado para representar certos vetores e outras estruturas de dados unidimensionais que não têm a estrutura matemática de um vetor, mas não é suficientemente geral para capturar todos os objetos que são vetores matemáticos (uma vez que não consegue distinguir entre vetores diferentes espaços).

Considerando a semântica de indexação de covetores no contexto de # 987 dá outra razão pela qual covectors não são AbstractVectors: não é possível em geral indexar v 'de forma barata porque tudo depende de quão caras quantidades como= v'_A_w e o custo de indexação é dominado pelo produto matvec A_w (ou A'_v), que certamente é muito caro para ser qualificado como AbstractVector.

Este é um tipo de argumento de loop. Como mencionado acima, um funcional linear (covetor) é mais geral e existe mesmo para espaços vetoriais sem produto interno. Em segundo lugar, quando a métrica é uma matriz A definida positiva, o mapeamento natural de um vetor v para um covetor é de fato v'_A. Porém, o que é isso aqui? Já é um mapeamento de v para um covetor diferente definido em relação à norma euclidiana padrão (com uma identidade como métrica)? Não não é. Se os vetores têm índices contravariantes (superiores) e covetores têm índices covariantes (inferiores), então a métrica A tem dois índices inferiores e o produto interno é= v ^ i A_ {i, j} v ^ j. E então esse mapeamento pode ser escrito como (com phi o covetor associado ao vetor v) como phi_j = v ^ i A_ {i, j}. (Observe que isso é diferente de um operador linear típico, que tem a forma w ^ i = O ^ i_j v ^ j). Portanto, v nesta expressão v'_A ainda é aquele com um índice superior, ainda é o mesmo vetor.

Então, na verdade, um dos pontos que eu estava tentando enfatizar acima, é que as pessoas costumam escrever v 'quando não estão realmente tentando trabalhar com um cobiçado. Eles apenas tentam escrever expressões que são definidas em vetores, como um produto internoou algo como v ^ i A_ {i, j} w ^ j dentro da representação de matriz familiar como v'_A_w. Mesmo o produto interno euclidiano padrão v'w deve ser lido como v ^ i delta_ {i, j} w ^ j e não requer a introdução de convetores. Como mencionei acima, acho que o uso de coletores reais adequados, que não são uma forma oculta de produtos escalares, é bastante limitado na maioria das aplicações. As pessoas simplesmente escrevem v 'para serem capazes de usar a sintaxe de matriz conveniente, mas o que elas estão realmente fazendo é computar produtos internos, etc., que são definidos com relação a vetores, não covetores.

Por outro lado, houve alguma observação antes sobre a associatividade da multiplicação se tivéssemos v '== v como na proposta original de Stefan. No entanto, mesmo sem isso, eu não diria que * é associativo, mesmo que v 'seja um covetor não identificado com vetores comuns:
A_ (v'_w) é uma matriz
(A_v ') _ w está produzindo um erro

O gradiente de uma função de valor escalar é de fato uma das aplicações adequadas de covetores, ou seja, sem argumento, o gradiente é um covetor. O que é implicitamente assumido em aplicações como gradiente conjugado é que existe uma métrica (e na verdade uma métrica inversa) para mapear esses covetores de volta aos vetores. Caso contrário, não há sentido em fazer algo como x ^ i (vetor de posição) + alpha g_i (gradiente). A maneira adequada de escrever isso é x ^ i + alpha delta ^ {i, j} g_j onde delta ^ {i, j} é a métrica inversa. Se alguém tentar definir técnicas de otimização em uma variedade com uma métrica não trivial, será necessário levar essa métrica (inversa) em consideração. Há um bom livro sobre isso:
http://sites.uclouvain.be/absil/amsbook/
e um pacote matlab correspondente:
http://www.manopt.org

Em 22 de outubro de 2014, às 22:52, goretkin [email protected] escreveu:

Já que estamos falando sobre DFTs e normas ... isso passou pela minha cabeça hoje

se é uma função de um vetor e produz um escalar, então eu gostaria que diff (f) fosse uma função que aceita um Vetor e produz um Covector de modo que f (x) ~ = f (x0) + diff (f) ( x0) * (x-x0). Um uso muito comum do gradiente é obter uma aproximação linear da mudança incremental na saída da função dada uma mudança incremental na entrada. Se a entrada for um vetor, então parece natural que o gradiente seja um covetor.

Mas se eu fosse implementar a descida de gradiente, precisaria adicionar (uma versão em escala de) o gradiente em x a ele mesmo. Nesse sentido, o gradiente é um vetor que aponta na direção mais acentuada, cuja norma é proporcional à taxa de mudança ao longo dessa direção mais acentuada.

Meu instinto diz que gradiente é um covector é mais importante.

-
Responda a este e-mail diretamente ou visualize-o no GitHub.

A_ (v'_w) é uma matriz
(A_v ') _ w está produzindo um erro

Oh droga.

b2e4d59001f67400bbcc46e15be2bbc001f07bfe05c7c60a2f473b8dae6dd78a

Este tópico estava atrasado para uma postagem com uma imagem engraçada.

@Jutho , admito que não estou usando a forma mais geral de espaço dual, mas não vejo como minha descrição é circular, dada minha escolha de definir um espaço dual usando a estrutura de um espaço de Banach. O formalismo espacial de Banach já é um exagero para espaços vetoriais de dimensão finita.

Circular não é o termo certo. O que eu estava tentando dizer com aquele parágrafo é que posso inverter essa linha de raciocínio em relação à avaliação eficiente das entradas, pois, por exemplo, no caso do gradiente (conjugado) em variedades curvas, acho que o gradiente (covetor) g_i pode ser calculado de forma eficiente, mas o vetor correspondente A ^ {i, j} g_ {j} que será adicionado a x ^ i (com A ^ {i, j} a métrica inversa) pode não ser eficiente. Acabei de notar o quão mal delineada minha mensagem anterior apareceu no Github; estava bem quando escrevi o e-mail. Tentei consertar agora. Não quero ficar me repetindo, nem quero dar a impressão (errada) de que não concordaria com as propostas atuais. Os únicos pontos que eu estava tentando fazer.

  1. O espaço dual é certamente um espaço vetorial, portanto seus elementos são vetores. No entanto, o objetivo não deve ser que todos os objetos que têm o significado matemático de um vetor sejam um subtipo de AbstractVector de Julia, nem todos os objetos que são subtipos de AbstractVector tenham as características matemáticas adequadas de um vetor. Nesse sentido, gosto muito mais do nome Matrix que Vector , pois tem muito menos conotação. Portanto, acho que posso concordar com o fato de que, seja qual for o tipo de Covector está sendo introduzido como parte desta proposta, não deve ser um subtipo de AbstractVector ou mesmo AbstractArray , mesmo nos casos em que V * é naturalmente isomorfo a V (espaço cartesiano, que representa provavelmente metade das aplicações).
  2. O produto interno permite que você construa um mapeamento de V a V_, mas não é um pré-requisito para a existência de V_. Certamente, isso soa como uma questão irrelevante, pois na programação você sempre tem um espaço vetorial de dimensão finita e sempre há um produto interno (mesmo que possa não ser o relevante para a aplicação), mas o ponto prático que eu estava tentando fazer é isso. Existem aplicações adequadas de covetores em programação, como o bom exemplo de gradiente de @goretkin , mas na maioria das aplicações onde as pessoas escrevem v' , elas não estão realmente tentando construir um covetor, estão apenas tentando escrever um bilinear mapeamento entre dois vetores (ou seja, V x V para escalar), como em v'_A_w = v ^ i A_ {i, j} w ^ j ou mesmo v'w = v ^ i delta_ {i, j} w ^ j usando a representação de matriz conveniente. Eu prefiro escrever dot(v,A*w) explicitamente, mas isso é uma escolha pessoal. Como não temos informações sobre índices superiores ou inferiores em matrizes, pode-se construir casos de uso onde em uma expressão escalar como v'*A*w ambos v' e w podem ser vetores ou covetores no sentido matemático. A única consequência disso é que eu não tenho certeza se estou realmente feliz em chamar o tipo de v' Covector , já que, assim como o nome Vector , tem muita matemática conotação que pode não ser justificada na maioria das aplicações, o que então leva a mais discussões (principalmente irrelevantes) como esta.

@jutho +1 para gradiente sendo covetores
Aprendi isso com a tese de doutorado de Steve Smith e usei essa ideia
para explicar claramente esta vaga ideia de gradiente conjugado para cálculos de autovalores
que as pessoas costumavam falar em química e física.

Estou cada vez mais surpreso com a consistência interna
e autocontido é o mundo dos tensores de classificação 2 com um índice superior e um inferior.
Isso é o que convencionalmente chamamos de cálculos matriciais ou simplesmente álgebra linear.
Heck, embora eu possa encontrar muitos exemplos como norm (v, p) ou fft (v) onde funções
diferem se forem vetores ou covetores, eu realmente não tenho um único exemplo bom (ainda!)
de uma função que é naturalmente diferente em um vetor e uma matriz de uma coluna.
(Alguém me ajude, com certeza deve haver um, até muitos !!)

Eu também, há vários anos, tenho me preocupado como @jutho, aquele vetor abstrato
os espaços ainda não tinham chegado a julia, lembro-me de conversar com @StefanKarpinski
sobre isso no meu quadro branco anos atrás. Ainda as preocupações gerais de
1) os recém-chegados à Julia devem ter uma experiência fácil e
2) desempenho
deve superar essas coisas chiques.

Como resultado de conversar com @jiahao , realmente me
Álgebra Linear (matrizes com um contra e um co) e existem tensores simples
(todo mundo é co, sem contras). Este último funciona bem em APL e Mathematica.
O primeiro envolve álgebra linear e foi melhor capturado no MATLAB antes
eles enxertados em matrizes de dimensão superior a 2. Há uma generalidade mais completa
que depois de uma palavra com @JeffBezanson parecia que não era tão louco.

a propósito, acho que demorou cerca de um ano, senão mais, mas finalmente estamos levando isso a sério :-)

A respeito de
A_ (v'_w) é uma matriz
(A_v ') _ w está produzindo um erro

Apenas a multiplicação de matriz "*" é associativa. A matriz de tempos escalar sobrecarregada nunca foi
associativo. Nem mesmo em matemática, nem mesmo em MATLAB.

A_ (v'_w) é uma matriz
(A_v ') _ w está produzindo um erro

Boa pegada. No entanto, há casos em que não erros produzem respostas diferentes? Uma questão relacionada: o que um covector escalar * deve fazer?

A matriz de tempos escalares sobrecarregados nunca foi associativa. Nem mesmo em matemática, nem mesmo em MATLAB.

Operações envolvendo apenas matrizes e escalares são associativas (ver ponto 3). Como podemos tratar vetores como matrizes de 1 coluna, incluí-los também não causa problemas. O problema aqui é que o Covector é um operador que pode reduzir dimensões (vetores de mapas para escalares), então não tenho certeza de como isso se encaixa.

A adição de matriz escalar é uma preocupação maior, pois isso arruína a distributividade (embora, se bem me lembro, acho que o debate resolveu mantê-la):

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

Este é um resumo extremamente bom:

Como resultado de conversar com @jiahao , realmente me
Álgebra Linear (matrizes com um contra e um co) e existem tensores simples
(todo mundo é co, sem contras).

É claro que esta discussão não é para apoiar o caso mais geral, isso é muito confuso para pessoas que não precisam de toda a generalidade, não pode ser incluído na hierarquia AbstractArray atual e é mais adequado para um pacote (que estou realmente trabalhando em). Mas a discussão é, de fato, qual desses dois mundos especiais queremos apoiar, já que eles não são mutuamente compatíveis.

No primeiro caso, todos os vetores v são colunas, todos v' são covetores e todas as matrizes são operadores lineares V-> W (por exemplo, eles vivem em W ⊗ V_) Isso exclui a representação de, por exemplo, bilinear mapeia V x V -> escalar (por exemplo, v ^ i A_ {i, j} w ^ j), pois requer uma matriz com dois índices mais baixos (por exemplo, vivendo em V_ ⊗ W_). Claro, você ainda pode usá-lo na prática e escrevê-lo como v'_A*w , mas meio que entra em conflito com a nomenclatura escolhida para os objetos. Além disso, ele não especifica em que espaço vivem as matrizes de ordem superior.

O último caso resolve esse problema, pois tem (V == V *), mas leva a resultados surpreendentes como v' == v . Além disso, esta solução é realmente limitada a espaços vetoriais reais, uma vez que mesmo em espaços complexos com um produto interno euclidiano padrão (ou seja, 'espaço cartesiano complexo'), o espaço dual não é naturalmente isomórfico a V, mas a conj (V) (V bar), o espaço vetorial conjugado (consulte os espaços de Hilbert em http://en.wikipedia.org/wiki/Complex_conjugate_vector_space)

Quanto à não associatividade, nesse aspecto o comportamento atual, onde v'*v não produz um escalar, mas um array, é na verdade mais consistente, desde então tanto A*(v'*v) quanto (A*v')*v produz um erro.

O lado "Álgebra Linear" das coisas pode ser adicionalmente resolvido em diferentes escolhas de como representar vetores e covetores:

  • O Covector, também conhecido como a proposta de "vetor de linha engraçada" que discutimos recentemente.
  • A semântica "sem vetores verdadeiros" que confunde (N-vetores como matrizes Nx1), (N-vetores linha como matrizes 1xN), que é endossada pelo Matlab e semelhantes.
  • A maneira atual de Julia - não misture N-vetores densos e matrizes Nx1, misture N-vetores esparsos e matrizes Nx1, represente vetores N-linha como matrizes 1xN.

Eu me pergunto se é estritamente necessário também combinar (escalares, 1-vetores e 1x1-matrizes) no mundo "sem vetores verdadeiros"; @alanedelman e eu estávamos discutindo isso ontem e na álgebra linear numérica a comutatividade do vetor * escalar e escalar * vetor é usada em todos os lugares, mas o vetor * produto escalar é aquele que não se importa se está fazendo (N,) * ( ,) ou (N, 1) * (1,1).

1) No final do dia, as coisas mais importantes são A) facilidade de uso e B) desempenho.
2) Os dois mundos devem ser capazes de coexistir em completa harmonia. Ao fazer um trabalho de engenharia, minha opinião é que não há nada mais intuitivo e fácil do que a notação tensorial. Uma sugestão, se possível, seria habilitar um sinalizador de ambiente ou um pacote poderia ser importado no início de um programa. Isso permitiria facilidade de uso e lógica de programação direta. Isso não é possível ou devemos escolher um esquema abrangente?

Parece que existem duas opções
1) permitir a importação de uma caixa de ferramentas, patch ou sinalizador ambiental para funcionar simultaneamente
2) construir uma linguagem que seja capaz de unificar os mundos especiais, ainda que seja fácil de usar e rápida

Não tenho certeza de como isso é promissor, mas acabei de descobrir uma área de pesquisa potencialmente interessante.

Posso dirigir sua atenção para:

Grupo de pesquisa de álgebra geométrica
http://www.mrao.cam.ac.uk/~clifford/pages/introduction.htm

Aula de Álgebra Geométrica
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/course99/

Aplicações Físicas da Álgebra Geométrica
http://www.mrao.cam.ac.uk/~clifford/ptIIIcourse/

Uma linguagem matemática unificada para a física e a engenharia no século 21
http://www.mrao.cam.ac.uk/%7Eclifford/publications/ps/dll_millen.pdf

Álgebra Geométrica
http://arxiv.org/pdf/1205.5935v1.pdf

Fundamentos da computação álgebra geométrica
http://link.springer.com/book/10.1007%2F978-3-642-31794-1

Computação Algebra Geométrica
http://link.springer.com/book/10.1007%2F978-1-84996-108-0

Depois de examinar isso um pouco mais, parece realmente incrível.

Enquanto a álgebra e a geometria foram separadas, seu progresso foi lento e seus usos limitados; mas quando essas duas ciências foram unidas, elas emprestaram a cada uma forças mútuas e marcharam juntas para a perfeição.

  • Joseph Louis Lagrange

Imagine precisar de óculos e não saber que você precisa de óculos. Então, quando você compra óculos, o mundo muda inesperadamente. GA é como óculos para o interior do cérebro.

  • Pablo Colapinto

Esse cara parece ter a ideia certa ... https://github.com/wolftype/versor

Bibliotecas de operação de matriz típicas têm funções embutidas de modelo para multiplicação de vetores e matrizes. A álgebra geométrica combina muitas outras matemáticas (álgebras de matriz, tensor, vetor e mentira). Versor é semelhante, mas em esteróides, onde vetores e matrizes esparsas de vários tamanhos são todos chamados de multivetores e representam elementos geométricos além das direções xyz e matrizes de transformação. Círculos, retas, esferas, planos, pontos são todos elementos algébricos, assim como os operadores que giram, torcem, dilatam e dobram essas variáveis. Ambos os elementos e operadores são multivetores que se multiplicam de muitas maneiras diferentes.

Este vídeo apresenta alguns bons detalhes sobre Versor, a linguagem matemática GA programada.
https://www.youtube.com/watch?v=W4p-e-g37tg

Estes são os slides para isso. https://github.com/boostcon/cppnow_presentations_2014/blob/master/files/generic_spaces.pdf

Você pode fazer alguns cálculos de tensores incríveis, realmente facilmente, otimizados para velocidade ao compilar.

@ esd100 , acho que seria útil manter a discussão neste tópico focada no problema específico do título.

@johnmyleswhite Eu fiz uma pesquisa pelo termo "tensor" e ele foi mencionado 171 vezes (agora 172 vezes). Embora eu concorde com sua afirmação, em geral, este tópico tem 157 comentários (agora 158), alguns dos quais lidam direta e indiretamente com o título da postagem original (Levando a sério a transposição vetorial). Acho que meu post lida indiretamente com o título do post original, oferecendo uma perspectiva ampliada do que pode ser feito com novas aplicações da matemática tensorial, via álgebra geométrica. Minha opinião é que construir o tipo de poder dimensional superior que Versor tem em Julia seria um recurso benéfico adicional de Julia. O título do vídeo do youTube era "Programação genérica de espaços genéricos: Álgebra geométrica em tempo de compilação com C ++ 11". Não vejo por que não poderia ser Julia em vez de C ++. Então, novamente, eu não sou um matemático ou cientista da computação.

@ esd100 Álgebras geométricas são interessantes, mas não são a coisa mais geral que esta edição cobre. A álgebra geométrica que estaríamos interessados ​​principalmente é a álgebra de Clifford real Cl (R ^ n, I); no entanto, também estaríamos interessados ​​em outras álgebras de Clifford que _não_ são álgebras geométricas, como álgebras de Clifford como vetores complexos Cl (C ^ n, I) ou mesmo álgebras sobre campos arbitrários ou anéis não comutativos Cl (F ^ n, I) . Além disso, não queremos nem mesmo nos restringir necessariamente às álgebras de Clifford, mas queremos considerar como a álgebra linear se generaliza para a configuração da álgebra tensorial mais geral, onde as formas quadráticas (produtos internos) não são necessariamente definidas.

Na configuração de álgebra tensorial, a proposta " v' é autônomo " no OP faz todo o sentido, uma vez que generaliza consistentemente para extensões bialgébricas que levam a tensores coalgebras onde o "v 'produz um covetor" a proposta é muito natural. Não tenho certeza de como a proposta de "nenhum vetor verdadeiro" se generaliza em uma álgebra de tensores.

@jiahao Obrigado pela sua resposta muito informativa. É bom ter alguém com algum domínio do assunto para lançar alguma luz. Terei interesse em examinar mais esses tópicos para obter um melhor entendimento. Estou curioso para saber por que existe código super otimizado para BLAS, mas código super otimizado para álgebras mais complexas, como a Álgebra de Clifford, não.

Não tenho certeza se essa discussão ainda está em andamento, mas gostaria de compartilhar minha opinião porque essas questões são algumas das partes mais importantes (e irritantes) de mim usando Julia. Algumas das opções acima eu concordo e outras não.

Basicamente, acho que precisamos perceber isso: todos os usuários do Julia vão querer arrays multidimensionais rápidos (para armazenamento de dados) conforme já implementado no Julia. Muitos / a maioria dos usuários também estarão interessados ​​em álgebra linear, e as matrizes mencionadas são uma maneira conveniente de empacotar os dados contidos em vetores e matrizes. Alguns usuários desejarão fazer álgebra de tensores genéricos (multilinear).

A questão é como estender essas matrizes "nuas" aos conceitos matemáticos da álgebra linear? Como você faz a sintaxe parecer matemática normal? As pessoas ficarão confortáveis ​​com v ''! = V? Etc etc.

A primeira coisa é, não queremos tornar os Arrays piores, apenas para que possamos fazer álgebra linear. Não queremos adicionar dados adicionais ao Vector ou argumentos de template desnecessários ao Array. Queremos ser tão rápidos quanto C / C ++ quando não estamos fazendo álgebra linear (e esperançosamente tão rápidos quanto C / fortran, quando o fazemos).

A segunda coisa que todos devemos perceber é que a contração do tensor multidimensional completa requer quantidades significativas de informações extras. Para tensores até 2 dimensionais escreva qualquer multiplicação como A_B'_C * D ... enquanto para tensores gerais é mais natural pensar em um gráfico para definir a contração (um diagrama de rede de tensores ou um gráfico de fator - a mesma coisa vale por muitos nomes diferentes). Para tensores de alta dimensão, faz sentido envolver os arrays nus e decorá-los com espaços vetoriais, etc. para manter o controle de tudo. Isso pode ser feito em pacotes como aqueles nos quais Jutho (e possivelmente outros) estão trabalhando. Não faz sentido considerar o significado de 'em tensores 3+ dimensionais - ninguém estaria interessado em usar essa função quando permutados existirem, ou se eles estiverem usando um pacote tensor dedicado.

Os usuários principais precisarão multiplicar matrizes, adicionar vetores e assim por diante. Eles vão querer transpor matrizes. Eles também vão querer um produto interno e um produto externo. Goste-se-ou-não, eles são definidos em conjunto com um espaço dual. Sabemos o que são para números reais e números complexos e os temos predefinidos. E sim, é verdade que o espaço dual de um espaço vetorial finito real parece basicamente a mesma coisa, e acredito que é isso que está obscurecendo toda a questão. O maior problema atual é que não temos um vetor dual adequado. Sem ele, não podemos "escrever" equações em Julia como fazemos na caneta e no papel - um grande problema! Ainda mais importante, não temos v '' = v. Isso é muito pouco intuitivo.

As pessoas só querem ou precisam criar um vetor duplo para realizar produtos internos ou externos. Um invólucro decorativo simples para dizer ao compilador o que fazer quando ele encontrar * é uma solução sensata e deve ter sobrecarga de tempo de execução zero. Acho que esses vetores / covetores duais devem ser indexáveis, adicionáveis ​​/ subtraíveis, multiplicados por um escalar, multiplicados por um vetor (retornando um escalar) e multiplicados por uma matriz (retornando um covetor) - e é isso! Eu nem mesmo realizaria explicitamente a conjugação complexa para vetores complexos - que poderiam ser incorporados ao produto escalar, produto externo, indexação, etc. para melhorias de velocidade / memória.

Não estou preocupado se estamos adicionando complicações ao sistema de tipos. Acredito que seja necessário encapsular a matemática conforme os usuários a aprenderam no ensino médio e na universidade: ter associativo * (quando bem definido), ter v '' = v, ter tanto "sutiãs" quanto "kets" (I estou usando a notação de Dirac de álgebra linear aqui), etc.

A propósito, se essas coisas foram implementadas no Julia 0.4, estou alheio! Ainda estou tentando entender 0.3.4 ...

@andyferris , geralmente concordo com tudo isso. Acho que minha proposta aqui poderia ser ligeiramente modificada e funcionar bem: em vez de M*v' ser um erro, faça com que ela produza um covetor e, em vez de v*M ser um erro, faça com que ela produza um vetor. Para mim, isso equivale a M ser agnóstico sobre se suas dimensões são para cima ou para baixo - você pode escrever v'*M*w ou v*M*w' para um produto interno e qualquer um deles funcionará.

Um pensamento que está mudando é ter matrizes de linha maior e maior de colo e M.' apenas mudar de um para o outro e, da mesma forma, v' ser a variação maior de linha em v - que tem a mesma forma, mas é conceitualmente armazenado na outra ordem. Não tenho certeza sobre isso embora.

+1 para @andyferris . Eu também acho que queremos apenas tipos de invólucro simples Transpose e ConjTranspose que nos permitem usar a sintaxe de álgebra de matriz concisa para escrever produtos de vetores, matrizes e transposições dos mesmos. Com relação ao ponto 2 da proposta de @StefanKarpinski , eu não restringiria esses wrappers a um contêiner de AbstractArray objetos e não tornaria os tipos parte da própria hierarquia de tipos AbstractArray . Transpose(x) deve ser apenas o tipo que resulta da escrita x' em uma expressão e permite adiar sua avaliação / ter avaliação preguiçosa, dependendo do resto da expressão, despachando Transpose para o resto das operações na expressão (que provavelmente será o operador de multiplicação * em 99,9% dos casos). No entanto, as pessoas também devem ser capazes de dar a essa sintaxe um significado para novos tipos que podem não fazer parte da hierarquia AbstractArray , como os objetos de fatoração de matriz ou outros tipos para definir, por exemplo, operadores lineares.

Para o caso específico de matrizes e vetores, não vejo realmente o caso de uso para M*v' , mas não sou intrinsecamente contrário a ele. O importante é que atenda às expectativas básicas (ou seja, regras 2,4,5,6 e 8 do final da proposta de Stefan).

O principal ponto final da discussão é provavelmente a interação com o fatiamento. Uma fatia de linha de uma matriz deve ser automaticamente Transpose ? Meu voto aqui é nas regras de fatiamento APL, onde este não é o caso.

@Jutho , a motivação é fazer * associativo - A*(v'w) e (A*v')*w trabalhar e produzir os mesmos resultados, módulo de não associatividade do tipo de elemento subjacente (por exemplo, flutuante- ponto).

Para que (A*v')*w fornecesse o mesmo resultado que A*(v'*w) , A*v' precisaria ser um array N=3 . É isso que você tinha em mente? Eu perdi completamente isso.

OK, muito interessante, tenho alguns comentários.

Primeiro, precisamos nos dirigir aos principais usuários. Basicamente, em primeiro lugar, usar o armazenamento Array{T,n} como n -dimensional é sua função primária. Tem que fazer sentido para os programadores que não têm nenhuma ideia sobre álgebra linear, mas que desejam manipular dados em Julia. Por causa disso, sob nenhuma circunstância uma fatia da matriz pode retornar um co-vetor! Este é um conceito de álgebra estritamente linear que se aplica apenas a certos tipos de dados T que podem definir um espaço vetorial e seu dual (por exemplo, dados numéricos ou "campos").

Eu poderia ir de qualquer maneira com o fatiamento APL. Parece bastante intuitivo. É de tipo estável e não faz nada de surpreendente. Embora eu não ache que seja necessário fazer essa alteração para tornar a álgebra linear autoconsistente ... pode ser bom (embora possa ser uma alteração importante). Acho perturbador que M[1,:] tenha o tamanho 1xn e M[:,1] tenha o tamanho n (não nx1) ... parece muito assimétrico ... mas acho que é um bom lembrete sobre como as coisas são colocado na memória. De qualquer forma, essa alteração só deve ser feita se fizer sentido para armazenamento e manipulação de dados abstratos. Para mim, essa mudança é ortogonal à discussão da álgebra linear.

Portanto, mesmo se não implementarmos regras de divisão de APL, ainda podemos salvar a álgebra linear significativa de maneira bastante direta. Se você quiser o vetor coluna i th de M chame colvec(M,i) e se você quiser o vetor co linha i th de M chame rowvec(M,i) . Se i fosse uma tupla ou vetor, poderia retornar uma tupla ou vetor ou (co) vetores (isso poderia ser útil para raciocinar sobre paralelização em alguns casos?). Se você preferir uma matriz, apenas use a notação de indexação normal. Se usarmos regras de divisão de APL, a mesma coisa é muito útil para distinguir a ação de fatias de linha e coluna com o símbolo * . (É preciso considerar como a conjugação complexa se comporta com rowvec ).

Dessa forma, se você estiver fazendo álgebra linear, tudo fará sentido e o usuário não terá que se preocupar com a regra de fatiamento específica que Julia implementa. O operador ' atuaria apenas em vetores e covetores (para mudar a decoração) e em matrizes (de forma direta ou preguiçosa). Pode ser mais fácil lembrar como extrair vetores de base de uma decomposição eletrônica, da qual sempre pareço esquecer.

Para * , acho que a álgebra de matriz / vetor em Julia deve funcionar de forma que se pareça com a matemática como o operador de multiplicação e nunca com o operador de produto tensorial entre dois vetores, dois covetores, duas matrizes, etc. não deve permitir M*v' , porque estritamente em matemática você precisa escrever a equação com o símbolo "\ otimes" (os sinais de tempo dentro de um círculo) e não o símbolo padrão de multiplicação. Usando o símbolo de multiplicação, seu significado está mal definido! Não podemos ter w*M*v' == v'*M*w porque a multiplicação da matriz não é comutativa. Novamente, para M*v'*v , não faz sentido falar sobre associatividade de * . Você pode interpretar isso como innerproduct(outerproduct(M,v'),v) que requer dois símbolos de multiplicação diferentes, ou como multiplicação escalar M * innerproduct(v',v) onde _faz_ sentido usar * para ambos. Com essa expressão, podemos exigir colchetes, ou talvez o compilador possa procurar uma ordem significativa de operações (observe aqui que a ordem de avaliação mais rápida é também aquela que considero a única ordem válida de avaliação).

Com vetores, covetores e matrizes, podemos ter um sistema algébrico consistente com a matemática padrão. Sempre que você deseja realizar o produto externo entre dois vetores, ou dois covetores, ou duas matrizes, você está inerentemente se movendo para a álgebra multilinear ou álgebra tensorial genérica. Aqui você possivelmente poderia ter matrizes e vetores que se multiplicam como kron(M,N) usando um novo símbolo. Ou você pode exigir que os usuários usem um pacote de álgebra multilinear completo. Ou seja o que for ... parece estar além do escopo da pergunta original (que foi há 14 meses, aliás ...)

Para resumir, vejo quatro coisas quase completamente distintas acontecendo aqui:

  1. Melhore o fatiamento de Array s de dados arbitrários usando as regras de fatiamento APL.
  2. Torne a álgebra linear em Julia mais intuitiva e totalmente consistente com a matemática, adicionando alguns conceitos simples: covetores e um método de extração de vetores e covetores de matrizes e operações entre covetores, matrizes, etc. Muitos usuários podem nem notar a existência de covetores, pois o uso de matrizes 1xn e nx1 continuará a funcionar conforme o esperado e o vetor se comportará da mesma forma.
  3. Para velocidade, implemente a avaliação preguiçosa para transpose , conj , etc, usando tipos de embalagem muito parecidos com covector, mas também para matrizes.
  4. Desenvolva um pacote tensor de propósito geral e possivelmente adicione-o à biblioteca padrão.

Possivelmente, apenas o primeiro será uma mudança significativa? Os outros melhoram ou adicionam funcionalidades atuais. Todos eles podem ser implementados de forma mais ou menos independente e, provavelmente, todos valem a pena fazer. (PS se você quiser fatiar APL, recomendo fazê-lo logo , antes que Julia fique muito "grande" e quebre muitos pacotes, etc).

Sim, desculpe, minha sugestão de permitir M*v' realmente não faz nenhum sentido, já que as diferentes maneiras de associar produtos produzem formas completamente diferentes. Então, eu acho que se vamos mudar isso, minha proposta original é o caminho a seguir. Até agora, essa parece ser a melhor combinação com o comportamento de fatiamento APL. Claro, se queremos ou não o comportamento de fatiamento de APL é uma consideração de nível mais alto.

Eu só queria dizer algumas coisas e, por favor, aceite-as com cautela. Eles são apenas opiniões e eu sei que os meus não têm muito peso. No entanto, uma coisa me surpreendeu enquanto eu lia o comentário de @andyferris . Eu não acho que concordo com o comentário de que tem que fazer sentido para programadores que não têm ideia sobre álgebra linear, mas que desejam manipular dados em Julia. Não acho que os programadores sejam o público para o qual os programas matemáticos devam ser escritos. Se um programador deseja manipular dados, existem muitas linguagens que permitirão que ele faça isso. Julia é uma linguagem para computação e deve permitir computação técnica sofisticada.

@ esd100 Verdade, eu pensei nisso. Mas com Julia, eu pensei que queríamos ser _grandes _... queríamos atender a muitos propósitos e nos sobressair em muitas coisas. Pode haver razões científicas genuínas para manipular dados não numéricos. Pode haver cientistas atuais ou ex-cientistas usuários de Julia que desejam fazer uma programação mais geral. Meu ponto anterior era que não devemos deixar Array mais lento ou tomar mais memória, dando a ele um campo extra para falar sobre transposição / conjugação.

Se alguém fizer o fatiamento de APL, as fatias de linha ou coluna devem retornar o mesmo objeto. A pergunta era: sem o fatiamento de APL, qual é a melhor coisa para M[1,:] retornar? Bem, se M[1,:,:,:,:] retornar um Array{T,n} , acho que confundiria a todos se M[1,:] retornasse um covector{T} . E se deixarmos M = zeros(3,3,3) e chamar M[1,:] , que atualmente retorna uma matriz 1x9? Devemos fazer disso um covetor também? E se M fosse Array{String,3} ?

Para mim, isso pareceria bastante surpreendente e confuso. Também não é consistente com as alterações de fatiamento de APL, se isso acontecer no futuro. Portanto, eu estava sugerindo adicionar novas funções para fins de álgebra linear, rowvec(M,i) e colvec(M,i) . Não é ideal, adiciona novas funções ... mas pelo menos está claro o que elas devem retornar para fins de álgebra linear. Foi a única coisa que pude pensar que tinha boas propriedades para matrizes genéricas e boas propriedades para álgebra linear (junto com um tipo de covetor), enquanto não tentava fornecer álgebra multilinear. Se alguém tiver uma notação melhor para extrair covetores de matrizes, isso também seria bom!

@ esd100 : Tenho certeza de que os programadores são o público para o qual Julia foi criada. A computação científica é o ponto forte do Julias, mas há muitos usuários do Julia que a usam apenas como uma linguagem de programação de propósito geral.

Eu concordo com @andyferris que é preciso separar os requisitos do tensor dos requisitos do contêiner.

Sem ter lido todo o tópico, meu problema pessoal é se eu tenho um array 3D A (por exemplo, dados tomográficos) e faço

imagesc( data[1,:,:] )

isso não funciona. IMHO data[1,:,:] , data[:,1,:] e data[:,:,1] devem ser matrizes 2D (ou submatrizes). Atualmente eu uso uma função squeeze autodefinida para resolver esse problema.

Novamente, esta é apenas a minha opinião e muito sem importância, visto que estou longe da ação. Eu sinto que quando um aplicativo é desenvolvido, ele deve ter um conjunto de princípios de design que o guiam. Os construtores devem enviar uma mensagem clara e unificada sobre seu propósito e sua identidade. Se o objetivo é desenvolver uma plataforma de computação técnica rápida, intuitiva e sofisticada, e esse é o ponto forte de Julia, mantenha-se firme. Não envie sinais confusos tentando fazer com que seu propósito seja adequado ao público de programadores gerais.

O ponto é que mesmo para aplicações puramente científicas ou matemáticas, existem várias interpretações conflitantes, por exemplo, existem vários objetos matemáticos que você pode representar usando vetores e matrizes e, portanto, não se deve fazer escolhas muito específicas.

É melhor ter pacotes dedicados para satisfazer as necessidades de disciplinas específicas seguindo um conjunto específico de convenções.

@jutho Minha intuição é que você está certo, mas me pergunto qual é a base de sua conclusão de que os pacotes são "melhores" e em comparação com quais alternativas?

Isso é muito simples. A funcionalidade que é importante para a maioria dos usuários Julia pertence ao Base. A funcionalidade que é mais especial pertence a um pacote.

Claro que não é muito fácil traçar uma linha aqui. Mas a melhor maneira de colocar algo na base é criar um pacote para que muitas pessoas possam testar isso. Diversas funcionalidades básicas que chegaram ao Base foram desenvolvidas em um pacote.

@ esd100 , não entendi totalmente sua pergunta. No final, o que uma linguagem científica deve fornecer são as estruturas e métodos de dados para representar e computar com objetos típicos usados ​​na ciência. Mas certas estruturas de dados podem ser úteis para representar diferentes estruturas matemáticas e, às vezes, diferentes representações podem ser convenientes para objetos do mesmo tipo. Conseqüentemente, associar uma estrutura matemática fixa a um tipo de dados pode ser muito restritivo para algumas disciplinas e muito complexo para outras disciplinas. Portanto, isso não deve ser perseguido na base de Julia, mas por pacotes específicos que apenas tentam atender às necessidades de uma disciplina.

Com relação à discussão atual, todos os que trabalham com vetores e matrizes irão esperar a possibilidade de transpor um vetor e ter v ^ T * w = escalar. Mas esses vetores e matrizes podem ser usados ​​para representar várias coisas diferentes, e isso provavelmente dependerá do campo / disciplina. Dei exemplos de onde v ^ T pode não ser um covetor real nas postagens acima.

Em 11 de janeiro de 2015, às 18:10, esd100 [email protected] escreveu:

@jutho https://github.com/jutho Minha intuição é que você está certo, mas me pergunto qual é a base de sua conclusão de que os pacotes são "melhores" e em comparação com quais alternativas?

-
Responda a este e-mail diretamente ou visualize-o no GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -69501771.

Isso é muito simples. A funcionalidade que é importante para a maioria dos usuários Julia pertence ao Base. A funcionalidade que é mais especial pertence a um pacote.

Não acho que seja tão simples. Por exemplo, existem funções Bessel no Base e é altamente improvável que sejam importantes para a maioria dos usuários Julia. A razão pela qual eles podem estar no Base é que há um padrão razoavelmente universal para o que são as funções de Bessel, e ninguém provavelmente usará esses nomes para outra coisa, ou esperará que eles façam algo diferente.

Por meio de convenções de nomenclatura cuidadosas (alguns podem dizer prolixas), o Mathematica é capaz de colocar mais de 4000 funções na linguagem principal, e acho extremamente conveniente quase nunca ter que carregar um pacote. Em contraste, quando estou usando Python / Sage, não é incomum que um arquivo / bloco de notas importe explicitamente 200 funções na parte superior (evitando a sintaxe mais indetectável "from ___ import *"). Sempre que preciso usar uma função que não seja integrada, tenho que passar por várias etapas extras:

(1) Tente lembrar se já o usei naquele arquivo e / ou faça uma pesquisa de texto para confirmar (restringindo a palavras inteiras se o nome for uma substring de outra coisa)
(2) Ou: (a) adicione a importância imediatamente, perdendo minha linha de pensamento e encontrando-a novamente; ou (b) tente se lembrar de adicionar a importação depois de fazer o que estava fazendo, mantendo outra coisa na memória
(3) Para funções menos comuns, possivelmente terá que procurar em qual pacote está

Isso pode ser irritante e debilitante. Então eu acho que, como as funções de Bessel, qualquer coisa que seja considerada padrão --- e, portanto, não causará conflitos de nome ou confusão --- deve estar no Base, independentemente de muitas pessoas usarem. Certamente álgebra linear.


De volta ao tópico, falamos sobre várias camadas de funcionalidade --- indo de arrays como contêineres semanticamente vazios (conforme implementado em Base.AbstractArray ), a objetos tensores cartesianos com semântica para cima / para baixo (conforme descrito por meu AbstractTensorArray proposta , para objetos tensores mais gerais com índices mapeados para espaços vetoriais (como no TensorToolbox de @Jutho ) --- de aumentar a generalidade e diminuir o potencial de otimização.

Acho que é importante que os usuários possam fazer a transição facilmente entre essas camadas, até porque os usuários _podem não saber_ de que nível de generalidade eles realmente precisam quando começam. Como um exemplo simples, @Jutho apontou que no exemplo de processamento de imagem de @jdbates , os usuários podem muito bem querer associar diferentes subconjuntos de índices a geometrias distintas, de modo a processar as coordenadas da imagem de uma maneira e o espaço de cores de outra; mas se eles tivessem começado usando apenas um deles geometricamente, seria conveniente fazer um upcast para uma representação mais geral que permita usar cada um com sua própria geometria. Se as funções adicionais (ou padrões de chamada de função) que atuam em cada nível de generalidade usam padrões razoáveis ​​--- por exemplo, upcasting up / down índices e índices neutros em AbstractTensorArray s para espaços vetoriais cartesianos padrão únicos e espaços de "sequência", respectivamente --- então se torna quase contínuo. Os usuários de uma camada inferior de funcionalidade não precisam saber ou se preocupar com as camadas superiores, até que precisem delas.

Qualquer parte disso para a qual seja claro e previsível --- para os usuários relevantes --- qual deveria ser a hierarquia, poderia ir razoavelmente na Base. A verdadeira questão deveria ser --- seja para operações de array, álgebra linear ou álgebra de tensores --- qual é a probabilidade de que um usuário desse tipo de funcionalidade espere ou queira de uma maneira diferente?

Muitas pessoas não gostam da granularidade ridícula de quantas importações você precisa para fazer qualquer coisa na terra dos Python, não acho que alguém está se propondo a ir tão longe.

Mas há um argumento importante de que um grande número de dependências e o inchaço da biblioteca são indesejáveis ​​para alguns casos de uso. Julia, a linguagem, realmente não deveria exigir a instalação de bibliotecas de tempo de execução Fortran em seu computador, mas no momento a biblioteca padrão Julia exige, seja ou não o código que você deseja executar está fazendo qualquer álgebra linear (ou funções de Bessel, ou FFT's, etc). Tudo isso é bem coberto pelo # 5155 e outros problemas - "instalado por padrão" e "mínimo necessário para qualquer funcionalidade" devem ser separados em diferentes conjuntos de módulos.

Obrigado Tony, eu concordo. Além disso, com nosso sistema de pacotes, não é realmente nenhum problema ter pacotes do tipo "caixa de ferramentas" que agrupam vários. Com a macro @reexport isso funciona bem.

Mas há outro ponto. Dentro de um pacote, é muito mais fácil levar uma ideia adiante. Se alguém deseja mudar algo, simplesmente o faz. Dentro da Base um é muito mais restrito.

Parece que, embora ter uma colcha de retalhos de pacotes permita uma maior independência, também cria uma plataforma fragmentada que é mais difícil de navegar. Parece que o uso de uma estrutura mais unificada e generalizada simplificaria e facilitaria a interação interdisciplinar e a exploração de novos domínios.

Qual é atualmente a maneira mais idiomática de fazer a matriz vetorial *?
Por exemplo, digamos que eu tenho X com a forma (K, N) e b com a forma (K,) , e quero multiplicar por b no esquerda para obter um vetor de comprimento (N,) .
Devo chamar BLAS.gemv('T',1.0,X,b)
Ou reshape(b'*X,size(x,2))
Ambos são um pouco feios.

Eu acho que você poderia fazer X'b

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

Qual é atualmente a maneira mais idiomática de fazer a matriz vetorial *?
Por exemplo, digamos que eu tenho X com forma (K, N) eb com forma (K,), e eu quero
multiplicar por b à esquerda para obter um vetor de comprimento (N,).
Devo chamar BLAS.gemv ('T', 1.0, X, b)
Ou remodelar (b '* X, tamanho (x, 2))
Ambos são um pouco feios.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79405868.

Sim, pensei que isso desencadearia uma cópia do X.
Mas @time parece indicar que não há cópia, com base na alocação de memória

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

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

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

Fazemos análise extravagante de 'então X'y termina como Ac_mul_B(X,y) e torna o
mesma chamada BLAS que você sugeriu.

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

Sim, embora eu tivesse pensado que isso desencadearia uma cópia do X.
(Mas @time https://github.com/time parece indicar que não há
cópia, com base na alocação de memória

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

julia> @time y'X;
tempo decorrido: 0,00177384 segundos (15 kB alocados)

julia> @time X'y;
tempo decorrido: 0,000528808 segundos (7 kB alocados)

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -79421713.

Acredite ou não, um artigo sobre todo esse tópico está cada vez mais perto de se materializar.

Um último ponto: alguém já considerou isso? poderia realmente ser um animal totalmente diferente de '? Este último tem a estrutura certa para ser dual, mas. ' não funciona se o campo subjacente não for os números reais. É uma loucura atribuir. ' a noção de "inverter todos os índices" de transpor e reservar todas as noções de dualidade para ', que produz um "vetor de linha engraçada" / conjugado hermitiano?

Acho que em qualquer caso conj(transpose(x)) deve ser equivalente a ctranspose(x) .

Como argumentado acima, espero que nenhum dos tipos de wrapper que transpose e ctranspose criarão receba um nome contendo um conceito matemático específico, como dual . Uma linguagem científica como Julia deve fornecer um conjunto de estruturas de dados úteis e deve implementar operações comuns, mas acho que seria um erro associar uma estrutura matemática estrita a ela, pois isso não será apropriado para algumas aplicações e muito complexo para outras. Cabe ao usuário usar essas estruturas de dados e operações para implementar as operações matemáticas em sua aplicação. Certamente existem aplicações para z.' * A * z retornando um escalar onde z é um vetor complexo e A alguma matriz, mesmo que isso não tenha nada a ver com um produto interno e / ou duplo vetores.

@jutho você poderia dar um caso de uso para z.' * A * z ?

Se z1 e z2 são a representação de dois vetores complexos Z1 e Z2 (por exemplo, vetores tangentes na parte holomórfica do espaço tangente de uma variedade de Kähler ) e a é a representação matricial de um tensor A com dois índices covariantes (por exemplo, uma forma complexa (2,0) da variedade de Kähler) então A(Z1,Z2) = z.' * a * z .

Observe que eu enfatizo aqui que os objetos julia z1 , z2 e a apenas formam uma _representação _ de certos objetos matemáticos (com relação a uma base / coordenação escolhida) e, portanto, operações em estruturas de dados não podem ser associadas exclusivamente a operações matemáticas sem saber o que essas estruturas de dados representam.

@jutho obrigado. Seu ponto de vista sobre representações é bem aceito e foi representado muitas vezes nesta discussão. Estou tentando encontrar a interface mínima de vetores e matrizes e ver se essa interface mínima é de alguma forma fundamentalmente incompatível com a interface mínima de matrizes e o que podemos descarregar para tipos de dados abstratos definidos pelo usuário.

Neste ponto, sou totalmente a favor da proposta @StefanKarpinski , também ecoada principalmente por @andyferris acima. Em particular

  1. Indexação de estilo APL
  2. v 'fornece algum tipo de Covector ou tipo de Transposição

Todo o resto é um detalhe. Pode ser bom adicionar funções row(M,i) e col(M,i) . Caso contrário, para extrair uma linha como um covector, acho que você precisaria de M[i,:].' ? IIUC, M[i,:]' faria um conj não desejado neste caso?

Sim, devo acrescentar que não apreciei totalmente a gentileza da indexação no estilo APL da última vez que escrevi, mas agora apoio totalmente essa alteração. Isso, por sua vez, torna o material de vetor duplo ainda mais atraente e as funções row / col .

Desde então, tentei brincar com uma implementação e a coisa mais confusa era se o covetor extraído de uma matriz era conjugado ou não ...

Eu acho que você quer pegar os valores literais da matriz. Usando a notação de Dirac, expanda (qualquer) matriz M = sum_i | i>é por exemplo [0,0,1, ..., 0], queremos extrairObtemos row(U,i)' = col(U’,i) que faz todo o sentido para extrair os autovetores esquerdo e direito de uma decomposição automática.

Andy

Em 15 de março de 2015, às 21h36, Jeff Bezanson [email protected] escreveu:

Neste ponto, sou totalmente a favor da proposta @StefanKarpinski https://github.com/StefanKarpinski , também ecoada principalmente por @andyferris https://github.com/andyferris acima. Em particular

Indexação de estilo APL
v 'fornece algum tipo de Covector ou tipo de Transposição
Todo o resto é um detalhe. Pode ser bom adicionar funções row (M, i) e col (M, i). Caso contrário, para extrair uma linha como um covetor, acho que você precisaria de M [i ,:]. ' ? IIUC, M [i ,:] 'faria um conj não desejado neste caso?

-
Responda a este e-mail diretamente ou visualize-o no GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -81228816.

Algum voluntário para começar a trabalhar nisso? por exemplo, @mbauman , @jakebolewski que estou surpreso em ver que ainda não estão neste tópico :)

Encontrar tudo o que precisa ser alterado pode ser entediante, mas a mudança básica no comportamento de indexação não deve ser tão ruim. Provavelmente @jiahao e @andreasnoack podem dizer mais sobre como os Covectors devem ser incorporados, por exemplo, qual deve ser seu supertipo.

Precisamos de 9, não de mais 8 comentários antes de prosseguirmos com isso.

Posso ajudar com isso.

estamos muito perto

Como um comentário relevante, se houver um tipo de invólucro Transpose e CTranspose , também deve ser um tipo de invólucro Conjugate simples, de modo que conj(A) seja também preguiçoso. Para multiplicar matrizes com BLAS isso não é realmente útil, uma vez que não há suporte especial para isso (e C em BLAS significa conjugado de hermit), mas se houver uma implementação completa de Julia BLAS, seria ótimo também apoiar conj(A)*B sem conjugação explícita.

Eu, por mim, sinto que agora levo as transposições de vetores muito mais a sério do que antes.

Talvez @andreasnoack e @simonbyrne possam nos dizer se # 6837 deve ser revisitado.

Concordo com @simonbyrne que não devemos ter Transpose{Array} <: AbstractArray .

Outros pensamentos gerais:

  • Percebi que o cálculo do produto externo atualmente contém uma exceção à regra "não adicionar dimensões singleton à direita automaticamente". Se u tem o tamanho (n,) , u * u' é um cálculo envolvendo (n,) x (1, n) . Este produto não pode ser calculado usando as regras comuns de multiplicação de matrizes _a menos que remodelemos automaticamente o primeiro argumento para ser (n, 1) .
  • A regra "adicionar dimensões singleton à direita automaticamente" da semântica de indexação do MATLAB é fundamentalmente incompatível com a regra "transpor reverte todas as dimensões". Com a regra anterior, as matrizes da forma (n,) são semanticamente idênticas às matrizes remodeladas da forma (n,1) e da forma (n,1,1) , etc. Mas observe que se a transposição inverte todas as dimensões, então os arrays resultantes têm a forma (n,) , (1, n) e (1,1,n) , que _não_ pode ser equivalente se você só tiver permissão para adicionar singletons à direita. Levando isso ao extremo lógico, a transposição de um array pode ter um número arbitrário de singletons _leading_ e, portanto, tem forma ambígua, que é logicamente inconsistente.

Eu também fiz uma pesquisa bibliográfica e descobri algumas histórias interessantes do APL. O que chamamos de regra de indexação APL não estava no livro de Iverson de 1962, mas existia em APL \ 360 (1968; a primeira implementação do APL). No entanto, o APL \ 360 combinou escalares e vetores 1, uma inconsistência que não foi reconhecida na literatura até (Haegi, 1976). Uma discussão da semântica formal de matrizes multidimensionais apareceu pela primeira vez na tese de doutorado de Brown (1972; ele mais tarde arquitetou o APL2) e estimulou uma linha de trabalho formalizando sua semântica.

Um excelente levantamento dos desenvolvimentos que levam ao APL2 é:

  • Karl Fritz Ruehr. "Uma pesquisa de extensões para APL." Em Proceedings of the International Conference on APL, APL '82, páginas 277-314, New York, NY, USA, 1982. ACM.

Notáveis ​​na literatura pela atenção dada às regras de indexação são:

  • T. More. "Axiomas e teoremas para uma teoria de matrizes." IBM Journal of Research and Development, 17 (março): 135–175, 1973.

    • Um tomo gigante formalizando a semântica de matrizes multidimensionais usando a teoria dos conjuntos axiomáticos de Quine, mostrando como escalares podem ser combinados com matrizes de classificação 0 com a noção de conjuntos autocontidos para construir o que a literatura APL chama de "matrizes flutuantes". ([1] == 1, em contraste com "matrizes aterradas" onde [1]! = 1. Um trabalho posterior de Sheila M. Singleton em sua tese de mestrado de 1980 mostrou que a teoria das matrizes de More também pode ser adaptada para descrever matrizes fixas.)

    • More também menciona o produto interno retornando um escalar como uma regra importante para orientar a semântica do array.

    • More também alude à complexidade de lidar com "up / down-ness" para matrizes multidimensionais gerais:

      "Seja V um espaço vetorial n-dimensional sobre um campo. Desconsiderando as considerações de contravariância e covariância, um tensor de valência q em V é um mapeamento multilinear do produto cartesiano da lista VV ... V de comprimento q em um vetor espaço. Se V tem uma base, então um tensor de valência q em V pode ser representado por um _tensor de componentes_, que é uma matriz em eixos q, cada um de comprimento n. "

  • G. Lewis. "Um novo sistema de indexação de matriz para APL", Anais da sétima conferência internacional sobre APL - APL '75, 234-239, 1975.

    • Este artigo foi o primeiro a defender a tupla de índice em uma operação de indexação para ser um objeto de primeira classe em APL, e observou que uma construção consistente do resultado da indexação pode ser alcançada por produtos cartesianos sistemáticos ao longo de cada classificação da tupla de índice.

  • Hans R Haegi. "A extensão do APL para estruturas de dados semelhantes a árvores." ACM SIGAPL APL Quote Quad, 7 (2): 8–18, 1976.

    • Este artigo reclamou de uma inconsistência no APL \ 360 clássico, que confundia escalares e arranjos 1, e defendia que a regra de indexação de APL exigia que essa fusão não se sustentasse.

    • Este artigo também contém uma construção muito semelhante a Lewis, 1975; o trabalho parece ser independente.

  • JA Gerth e DL Orth. "Indexação e fusão em APL." Em Proceedings of the International Conference on APL, APL '88, páginas 156–161, New York, NY, USA, 1988. ACM.

    • Observou que a regra de indexação APL pode ser justificada pensando na indexação como uma operação de transmissão que mapeia o conjunto de índices para o conjunto de valores. Esta interpretação funcional sugere naturalmente a regra de preservação de classificação e a construção cartesiana de Lewis e Haegi.

Além disso, sem a regra "pode ​​adicionar dimensões de singleton à direita", não obedecemos a identidade

image

já que o lado esquerdo é um escalar (por definição de traço) e o lado direito tem a forma (1, n) x (n, n) x (n,) = (1,) . Por outro lado, podemos tomar essa identidade como um princípio orientador para a seleção da semântica de transposição vetorial. O ponto principal é a propriedade cíclica da operação de rastreamento que define a primeira identidade. A quantidade dentro do traço deve ser um escalar ou uma matriz (o traço de um vetor não está definido). Avv' já é inequivocamente uma matriz: (Av)v' e A(vv') produzem o mesmo resultado. Mas também da segunda quantidade, v'Av deve ser uma matriz _ou_ um escalar. (Se escalar, a segunda identidade também é válida.) v'Av pode ser um vetor de 1 apenas se a regra "pode ​​adicionar dimensões singleton à direita" estiver ativa, caso em que pode ser remodelado de forma transparente para uma matriz 1x1.

Portanto, se quisermos que o traço seja definido, ele necessariamente impõe restrições sobre as formas permitidas dos produtos externos vv' e das formas quadráticas v'Av .

@jihao : Não tenho certeza do que você está defendendo ou contra. Você pode afirmar a "regra pode adicionar dimensões do singleton à direita" um pouco mais claramente? Quando isso se aplica? Você acha que devemos apoiá-lo?

Acho que alguns de seus argumentos podem ser usados ​​para fortalecer a posição de que não podemos pensar na transposição como a reversão de todas as dimensões (um vetor coluna seria transposto para um vetor coluna, ou possivelmente uma matriz com um número arbitrário de dimensões singleton iniciais). Para permanecer consistente com a álgebra matricial, acho que deve ser interpretado como uma troca das duas primeiras dimensões. Então eu acredito que algumas das contradições que você afirma desaparecem. Felizmente, o resto desaparece se não adicionarmos nenhuma dimensão singleton durante a transposição (a ausência da primeira dimensão do covetor não conta).

Meus comentários gerais acima não são para defender essas regras, mas sim para ajudar a delinear o espaço de design das regras de indexação de array e sua interação com as identidades da álgebra linear.

A regra "pode ​​adicionar dimensões de singleton à direita" é usada pelo MATLAB e (de acordo com @alanedelman) foi introduzida para oferecer suporte a matrizes multidimensionais. O cálculo de índice para deslocamento usado em matrizes MATLAB é definido por sub2ind , que retorna o mesmo índice linear, independentemente de quantos 1s à direita você joga nele. Além disso, a documentação de indexação de matriz do MATLAB afirma que, para operações de indexação:

O número de subscritos especificados para B, não incluindo subscritos finais iguais a 1, não excede ndims (B).

Mais formalmente, acho que pode ser declarado como:

Para operações que usam arrays como entrada, (n,) -arrays, (n,1) -arrays, (n,1,1...) arrays estão todos na mesma classe de equivalência no que diz respeito a serem argumentos válidos para essas operações. (Se n=1 , a classe de equivalência também inclui escalares.)

Exemplos:

  • A*b e A\b onde A é uma matriz e b pode ser um vetor ou uma matriz (semântica idêntica para n x 1 matrizes),
  • hcat(A, b) onde A é uma matriz e b pode ser um vetor ou uma matriz

Na maioria das vezes, Julia não tem essa regra "pode ​​adicionar regras de dimensões singleton à direita", exceto para o exemplo de produto externo. Possivelmente também há outros, mas não consigo pensar neles agora.

Contanto que Transpose{A<:AbstractArray} não seja um subtipo de AbstractArray , acho que não vejo um problema (mas provavelmente estou esquecendo algo porque não pensei tanto nisso quanto vocês) :
com

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

(e da mesma forma por CTranspose ), podemos ter

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

A única questão em aberto é se AbstractVector * AbstractTMatrix deve ser suportado quando AbstractTMatrix tem 1 como primeiro tamanho, ou se AbstractVector * AbstractVectorTranspose é suficiente.

Além disso, o novo sistema de tipos pode ajudar a expressar alguns daqueles typealias e union um pouco mais de cuidado.

Além disso, se por exemplo v.'*A é calculado como (A.'*v).' , então a necessidade de um invólucro Conjugate aparecerá se A si for, por exemplo, A=B' .

Concordo com @simonbyrne que não devemos ter Transpose{Array} <: AbstractArray .

Você pode elaborar aí? Achei que a opinião em https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215 era que CoVector não deveria ser um subtipo de AbstractVector, mas me pareceria um pouco estranho não ter Transpose{Matrix} <: AbstractArray .

Pelo que vale a pena, acho que CoVector deve se comportar principalmente como Vector exceto que Vector converte em Matrix como uma matriz de coluna enquanto CoVector converte em Matrix como uma matriz de linha.

Eu acho que isso significaria que a indexação em um covector deve funcionar da mesma forma que em uma matriz de linha.

É um pensamento interessante. As coisas ficam mais fáceis ou mais complicadas se apenas as dimensões do singleton _lider_ são eliminadas nos tipos de transposição / covetor?

(Tenho acompanhado essa questão com interesse, mas minha álgebra linear está enferrujada o suficiente para que não me sinta qualificado para contribuir).

@mbauman :

As coisas ficam mais fáceis ou mais complicadas se apenas as dimensões principais do singleton forem eliminadas nos tipos de transposição / covetor?

Se você permitir um número arbitrário de dimensões singleton iniciais, os índices de matriz não serão mais bem ordenados e não haverá "a" primeira dimensão, o que é bizarro o suficiente para que possamos considerar a ordenação como um axioma.

@tkelman :

Achei que a opinião em # 4774 (comentário) era que CoVector não deveria ser um subtipo de AbstractVector, mas me pareceria um pouco estranho não ter Transpose {Matrix} <: AbstractArray.

Ficou claro para mim que esse problema é inteiramente sobre como separar a semântica de array (cf # 10064) da semântica de álgebra linear e procurar lugares onde se misturam.

  • a semântica da matriz é definida por funções como tamanho, comprimento, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • semântica de álgebra linear é definida por funções como +, -, *, /,, ', trace ...

Se definirmos as funções cat como parte da interface essencial de um AbstractArray , então Transpose{<:AbstractArray} claramente não é um AbstractArray porque seus comportamentos de concatenação são diferentes . Se considerarmos apenas a forma e a indexação como parte da interface essencial, a situação é menos clara.

Se exigirmos a concatenação como parte da interface essencial de um AbstractArray , então também é mais fácil justificar por que tipos como SymTridiagonal não são AbstractArray s, já que as operações de concatenação em SymTridiagonal s como [SymTridiagonal(randn(5), randn(4)) randn(5)] estão atualmente indefinidos.

@toivoh :

Eu acho que isso significaria que a indexação em um covector deve funcionar da mesma forma que em uma matriz de linha.

Existem identidades que sugerem que os comportamentos de indexação de Transpose{Vector} devem ser iguais aos de Vector s comuns. Considere que para os tipos numéricos, v[1] produz o mesmo resultado que v' * e₁ = v ⋅ e₁ e v[1:2] produz o mesmo resultado que v' * [e₁ e₂] , onde e₁ é a base canônica Vector{Int} [1, 0, 0, ...] e e₂ é [0, 1, 0, 0, ...] . Se exigirmos que essas identidades relacionadas à indexação, aos produtos internos e à transposição sejam mantidas, pode-se afirmar que

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

(onde a primeira etapa é um novo axioma e a quarta faz uso do fato de que transpor um escalar é autônomo) de modo que a indexação em Transpose{Vector} essencialmente ignoraria a transposição e a indexação em CTranspose{Vector} conjugaria os elementos indexados.

me que esse problema é inteiramente sobre a separação da semântica de array (cf # 10064) da semântica de álgebra linear e procurando por lugares onde se misturam.

  • a semântica da matriz é definida por funções como tamanho, comprimento, getindex, setindex, hcat, vcat, reshape, rot90 ...
  • semântica de álgebra linear é definida por funções como +, -, *, /,, ', trace ...

+1 nesse ponto de vista e não ter Transpose <: AbstractArray . Além disso, ao indexar um covetor, deve ser com um único índice, caso contrário, o resultado do covector * vetor (contração sobre um único índice) não poderia resultar em um escalar (um objeto com índices zero).

@jihao : Não sei por que precisamos ou queremos

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

como um novo axioma. Mesmo que um covector indexasse como uma matriz de linha, acho que obteríamos o mesmo resultado para o acima devido à indexação linear.

E +1 para ver os covetores preocupados com álgebra linear.

Mas não há razão para que a concatenação com SymTridiagonal não deva ser definida, certo?

A indexação linear

Achei que a indexação linear era sobre a ordem de armazenamento e, de qualquer forma, não há outra ordem sensata para a indexação linear de um vetor, certo? (E desculpe pelo erro de ortografia.)

Não existe uma única ordem de percurso sensível na memória. Mesmo para matrizes Fortran, você pode escolher armazenar os elementos na ordem principal da coluna, linha principal ou até mesmo ordem principal da coluna reversa (que é exatamente o que o compilador IBM Fortran I original fez). Além disso, existem outras estruturas de dados (consulte # 10064), como tentativas, que podem ser usadas como arrays e têm ainda mais opções para a ordem de passagem.

O mesmo poderia ser dito sobre vetores e matrizes. Já que a indexação linear acessa os elementos na mesma ordem para uma matriz de coluna e sua transposta (e a mesma que para um vetor de coluna), por que um covetor deveria ser diferente? Se fosse diferente, acho que deveria ser que não definiríamos indexação para covetores.

@toivoh sim, as mesmas definições são

A indexação de Transpose objetos pode não ser permitida. Não estou dizendo que eles precisam ser indexáveis, mas se forem, não precisam necessariamente ter o mesmo comportamento de indexação. Para ser conservador, podemos deixar a indexação indefinida por enquanto e ver se algum caso de uso aparece.

Muitas implementações de Julia puras de funções de álgebra linear desejarão indexar em uma Transposição, não? Escrever multiplicação de matriz Julia pura (para tipos de número não BLAS) se torna fácil se você não tem que distinguir entre nenhum caso possível (normal, transpor, ctranspor, conj?) Para as duas matrizes envolvidas, mas apenas tratá-las como matrizes comuns. Métodos esquecidos do cache podem ser tentados para obter um padrão de acesso à memória local.

Certo, duh.

@Jutho : Eu concordo. E para caber lá, os covetores devem indexar como matrizes de linha, certo?

@toivoh , se você quer dizer que eles deveriam ter um índice 1 extra na frente, eu discordo e não vejo como isso está implícito em minha declaração. Eu estava falando apenas sobre produtos de matriz de matriz. Matrix * vetor ou covector * matrix são métodos diferentes que requerem diferentes definições de função, não apenas porque têm um padrão de acesso à memória diferente, mas também porque têm um tipo de retorno diferente (Matrix_vector = vetor ou covector_matrix = covector), então há um razão muito prática em Julia para não misturar essas coisas.

Em geral, não sou um grande fã da habilidade de adicionar índices extras 1 ao indexar uma matriz N-dimensional, ou, por exemplo, do apelido do tipo VecOrMat . Isso permite uma programação desleixada, mas é também por isso que facilita cometer erros ou detectar outros erros mais lentamente. Eu só vejo duas maneiras úteis de encontrar um array N-dimensional, ou seja, com índices N exatos, no caso de você estar usando-o como um objeto multilinear, ou com um índice linear, quando você o está tratando como um vetor em um produto tensorial espaço (por exemplo, para adicionar duas dessas matrizes ou multiplicá-lo com um escalar). Embora isso seja suficiente para o meu uso caes, posso aceitar que seja limitado para outros.

@Jutho : Ok, concordo que provavelmente não importa, pois o tipo de retorno deve ser diferente de qualquer maneira.

Aqui está uma tentativa de descrever o que estamos tentando fazer e fornecer alguns axiomas:

Acho que chegamos a um consenso bastante claro de que o ponto de partida é a álgebra matricial (que se baseia exclusivamente em matrizes). Para a maioria das operações, sabemos como eles se comportam na configuração de matriz pura.

Acredito que o que estamos tentando fazer é estender e refinar a configuração da matriz pura de uma maneira consistente para termos também escalares e vetores verdadeiros, e porque parece ser necessário para consistência, covetores.

Aqui está minha visão de escalares e vetores (como visto do ponto de vista de matriz pura): Um escalar é uma matriz, que por seu tipo é restrita a 1 x 1. Um vetor é uma matriz, que por seu tipo é restrita a seja nx 1. (argumentarei abaixo que um covetor é uma matriz que por seu tipo é restrita a ser 1 x n.) Deste ponto de vista, podemos dar dois axiomas: (descrito não muito formalmente abaixo)

  • Extensão: Considere uma função de matrizes para matrizes. Se ele sempre produzirá uma matriz de saída com tamanho 1 em uma dada dimensão, dadas entradas de certos tipos, esse fato será codificado no tipo de saída (tornando-a um escalar / vetor / covetor).
  • Refinamento: Se uma função que levaria um argumento de matriz na configuração de matriz pura requer que a entrada tenha um tamanho de um em uma ou mais dimensões, ela pode se recusar a aceitar uma entrada onde esse fato não estiver codificado no tipo.

Se concordarmos com o acima, a transposta de um vetor deve ser o tipo de covetor descrito acima: A transposição da matriz ans 1 produz uma matriz 1 xn. Se codificarmos o fato de que o tamanho ao longo das primeiras dimensões do resultado é sempre um, obtemos o covetor conforme descrito acima.

Se você começar do ponto de vista da álgebra matricial, o que você diz está correto. Este é o modelo que o MATlab provavelmente implementa perfeitamente. Tudo é uma matriz. É um sistema fechado, todas as operações em matrizes produzem novas matrizes.

Certamente tive a impressão de que o objetivo dessa questão (levar as transposições vetoriais a sério, já que não são apenas matrizes) era fugir desse modelo de álgebra matricial, porque ele começa a mostrar inconsistências se você quiser separar números de matrizes 1x1 por motivos de eficiência. A alternativa é então seguir o modelo de álgebra linear , onde há uma distinção clara entre o campo (escalares), o espaço vetorial (e seu dual correspondente) e o espaço de operadores / transformações lineares (matrizes).

Eu acho que as operações de álgebra linear em Julia estão fortemente enraizadas na tradição do matlab, com algumas exceções notáveis ​​como ter escalares e vetores, e não tentar adivinhar o usuário. Qualquer coisa que se afaste muito disso é provavelmente uma perturbação muito grande.

Eu acredito que meus axiomas acima devem ir ao longo do caminho para resolver as inconsistências que aparecem quando você deseja separar escalares e vetores de matrizes (por razões de eficiência e correção). Mas estou definitivamente aberto para ouvir sobre outros sistemas possíveis.

Eu concordo com @Jutho aqui; todo o ponto desta questão é se afastar da semântica "tudo é uma matriz" do MATLAB. A regra do MATLAB "pode ​​adicionar dimensões singleton à direita" é necessária para que o universo seja fechado sob operações de álgebra linear, mas essa regra define classes de equivalência que contêm membros do tipo T e outros do tipo Array{T,N} para todos N , e é a principal razão pela qual o sistema de tipos do MATLAB é indecidível. (Veja o Teorema 1 de Joisha e Banerjee, 2006 - embora o resultado seja declarado em termos de formas, o problema realmente é como mudar a classificação da matriz pode mudar a semântica do programa.)

Mas ainda acho que tivemos um bom consenso sobre que a multiplicação de escalares, vetores, covetores e matrizes deve ser associativa (com exceção de coisas como (v'*v)*v onde dois não escalares se multiplicam para produzir um escalar), e que por exemplo, v'*M*v deve produzir um escalar, M*v um vetor e v'*M um covetor. Não tenho certeza do quanto é possível desviar-se da semântica tudo-é-uma-matriz e ainda preencher essas condições.
O que mais estamos tentando evitar e quais propriedades gostaríamos de obter?

O quão pior seria se nós apenas combinássemos T e Array{T,0} em alguns casos? (Por exemplo, indexação: M[:,2] produz um Array{T,1} , mas M[2,2] não produz um Array{T,0} )

Ainda podemos manter uma semântica válida com Transpose{Vector} .

Eu reli todos os comentários sobre associatividade e concluí que a maior parte dessa discussão não é realmente sobre associatividade em si, mas sim a semântica de produtos internos e externos. Se você encontrar uma expressão que não seja sobre nenhum dos dois, indique-a.

O problema com a semântica do tipo Matlab é que M*v' e v*M às vezes funcionam, embora não devessem. Se M é m x 1 então M*v' é válido e retorna uma quantidade externa similar a um produto (já que v' é 1 x n ). Da mesma forma, se M for 1 x m e tivermos a regra "pode ​​adicionar singletons à direita", então v*M pode ser avaliado como o produto de n x 1 e 1 x m Matrizes

A questão de confundir T e Array{T,0} também foi levantada na literatura APL - em APL, os arrays multidimensionais são aninhados recursivamente, o que levanta a questão de se Array{T,0} e T são distinguíveis. Se não, eles são "matrizes aterradas" (que recuam para T ), caso contrário, são "matrizes flutuantes" (que recurvam para Array{T,0} apenas). Eu acredito que More, 1973 realmente provou que qualquer uma das escolhas é axiomaticamente consistente. Não tenho certeza se o APL já resolveu a questão de qual usar antes que a maioria dos praticantes se aposentasse ou mudasse para outra coisa.

@jiahao : Não percebi o quão fundamental era a sua observação de que

v[i] = e_i' * v

para amarrar álgebra linear e semântica de indexação. Mas então você também deve considerar

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

que indica que um produto interno com um vetor de base da direita corresponde à indexação ao longo da segunda dimensão. Assim, eu sustentaria que a entrada i th de um covetor v' deve ser indexada como

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

onde, é claro, gostaríamos de escrever algo diferente de 1 como o primeiro índice, mas o quê?
Enfim, já que permitimos

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

então 1 deve ser permitido como um espaço reservado também no caso acima.

v' * e_i é um escalar, então e_1' * (v' * e_i) é um covector, não um escalar. Portanto, as dimensões não correspondem para pensar em v'[1, i] = e_1' * v' * e_i

editar: Eu acho que isso pode ser um argumento contra permitir singletons à direita na indexação?

Sim, a indexação da matriz é o próximo passo lógico, e e_i' * M * e_j é na verdade uma das expressões onde o axioma de associatividade se torna útil, uma vez que

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

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

deve ser igual. A indexação de matriz pode ser derivada de uma regra de indexação de vetor e uma regra de indexação de covetor.

Acredito que uma resolução consistente desse problema pode exigir que expressões como v[i, 1] não sejam permitidas, uma vez que a regra que permite esse comportamento de indexação
a) deve causar casos espúrios para A*v' e v*A funcionarem (o primeiro funciona, mas o último não porque aplicamos a regra do singleton à direita inconsistentemente), e
b) se considerarmos a igualdade de v[i] = v[i, 1, 1, 1] então a regra de indexação de covetor correspondente seria semelhante a (v')[1, 1, 1, i] e deve-se ter a regra correspondente "permitir número arbitrário de singletons principais" para covetores para consistência. Acho muito preocupante a falta de uma primeira dimensão definida de maneira única.

Não acho que esse raciocínio de índice realmente vá a lugar nenhum. A indexação é uma propriedade geral dos arrays N -dimensionais e que podem ser escritos como expressões de matriz simples para N=1 ou N=2 é um tanto trivial e não contém nenhum fundamento em formação. Mas isso não generaliza para matrizes de classificação superior e, portanto, não deve ser usado para motivar se um covector precisa de um 1 inicial ou não ao ser indexado. Isso leva rapidamente a inconsistências, conforme observado nas postagens anteriores.

Como afirmado acima, nunca fui fã de confiar em índices únicos e não conseguia pensar em uma única situação em que isso fosse necessário ou mesmo realmente útil. Mas posso aceitar que meu ponto de vista é muito limitado.

Eu não digeri totalmente tudo isso, mas minha questão principal é simplesmente: size(covector) igual a (n,) ou (1,n) ?

Se eles não fizerem parte da família AbstractArray , não é estritamente necessário que size seja definido.

Embora, por razões práticas, eu ache que será definido (como é para números, etc.), e meu voto vai para (n,) . Do ponto de vista do armazenamento / contêiner, eu diria que não há distinção entre vetores e covetores. Portanto, também não há necessidade de usar covetores como contêineres e, portanto, eles não pertencem à hierarquia AbstractArray . É apenas um tipo de invólucro simples para expressar que eles se comportam de maneira diferente dos vetores em relação às operações de álgebra linear.

"Enquanto a álgebra e a geometria foram separadas, seu progresso foi lento e seus usos limitados; mas quando essas duas ciências foram unidas, elas emprestaram a cada uma forças mútuas e marcharam juntas para a perfeição." - Joseph Louis Lagrange

Gostaria de poder contribuir mais, mas pensei em apenas dizer que sou a favor de um sistema que favoreça a criação de ferramentas matemáticas sofisticadas empregadas por físicos e engenheiros intuitivas e precisas. Talvez, este recurso do MIT, possa ser útil ...

http://ocw.mit.edu/resources/res-8-001-applied-geometric-algebra-spring-2009/lecture-notes-contents/

Na verdade, pensando bem, é mais justo dizer que

e_i' * x = x[i, :] # x is a vector or matrix
x * e_j  = x[:, j] # x is a covector or matrix

Então, para indexação de matriz, teríamos

e_i' * M * e_j = e_i' * (M * e_j) = e_i' * M[:, j] = M[:, j][i, :] = M[i, j]
e_i' * M * e_j = (e_i' * M) * e_j = M[i, :] * e_j  = M[i, :][:, j] = M[i, j]

Atualmente, isso não se aplica a Julia, por exemplo, v[i, :] atualmente produz um array 1x1 e não um escalar. (Mas talvez não devesse)
A associatividade da multiplicação da matriz em e_i' * M * e_j corresponde à comutatividade do corte ao longo de diferentes dimensões M[:, j][i, :] = M[i, :][:, j] , que parece ser uma característica desejável.
Pela linha de raciocínio acima, devemos ter

v'[:,i] = conj(v[i])

@Jutho : Acho que esse paradigma de "indexação como e_i , etc., também em qualquer ordem (contanto que você mantenha o controle de quais dimensões correspondem).

Acho que esse paradigma de "indexação como fracionamento repetido" generaliza para matrizes / tensores de dimensões mais altas: você aplica um fracionamento para cada dimensão para indexar, em qualquer ordem. Isso corresponde a uma série de contrações com tensores de primeira ordem correspondendo a e_i, etc, também em qualquer ordem (contanto que você mantenha o controle de quais dimensões correspondem).

Sim, estou muito familiarizado com contrações de tensor etc, e de fato obter um elemento de matriz / elemento tensor corresponde a tomar um valor esperado na base computacional padrão, ou seja, contrair com alguns vetores de base (desde que você tenha uma base ortogonal pelo menos ), mas não há associação única para saber se um índice corresponde a uma contração com e_i ou com e_i' . Isso corresponde a decidir se o índice correspondente aparece em uma posição covariante ou contravariante, e não há uma decisão única. Todas as combinações possíveis de índices superiores e inferiores têm aplicações úteis. Mas contratar e_i e e_i' não é nem mesmo a maneira correta de colocar esta questão do ponto de vista matemático, porque, como afirmei acima, não existe realmente nenhum mapeamento matemático como a transposição de um vetor. A transposta é uma operação definida para mapas lineares (matrizes) e mesmo assim só corresponde à transposta da matriz se você também escrever vetores duais como vetores coluna. A transposição de um vetor é apenas um truque de conveniência que foi introduzido na álgebra de matrizes (onde de fato os vetores são n x 1 matrizes) e só funciona porque você está em um espaço euclidiano onde existe um mapeamento canônico do (conjugado ) espaço vetorial para o espaço dual.

Em particular, se você quiser as propriedades que descreveu acima, você deve fazer com que M[i,:] retorne um objeto diferente (uma matriz 1xn ou um covetor), em seguida, M[:,i] (que deve ser uma matriz ou vetor nx1 ). O fato de que isso não generaliza claramente para dimensões superiores é exatamente um dos principais pontos de discussão deste problema, e parece que a maioria das pessoas é a favor da indexação APL, onde as dimensões indexadas com um número são eliminadas (ou seja, ambas M[:,i] e M[i,:] produzem um array de classificação 1, portanto, um vetor). A indexação é uma propriedade das matrizes, e é essa mistura do comportamento da indexação com as operações de álgebra linear que resulta em todas as confusões / inconsistências em primeiro lugar. Pode ser consistente contanto que você permaneça dentro do ecossistema fechado de objetos de classificação N=2 , ou seja, tudo é uma matriz, também vetores e números, e você nunca considere matrizes dimensionais mais altas.

Eu também percebi que M[i,:] teria que produzir um covector pelo meu raciocínio acima, como você disse. Portanto, parece que ter covetores é, em algum nível, fundamentalmente inconsistente com a indexação APL. Se somos a favor da indexação APL (e eu gosto dela), a questão é onde traçar a linha entre aquele mundo e o mundo onde vivem os covetores. (Eu esperava que fosse possível conciliar os dois, talvez o resto de vocês já tenha percebido que teríamos que desistir disso?)

Este conflito talvez não seja tão surpreendente se você pensar sobre isso:

  • Na indexação APL, apenas dimensões significativas e indexáveis ​​são mantidas no resultado; o resto é espremido.
  • Se fizéssemos isso com v' , teríamos v' = conj(v) . Em vez disso, o covetor pode ser visto como um rastreador de uma primeira dimensão ausente.

Eu acho que um bom começo seria restringir os covetores tanto quanto possível, basicamente apenas definindo multiplicação, adição / subtração e divisão à esquerda neles.

A ideia parece não ser <: AbstractArray . Faria sentido tê-los como subtipos de um novo tipo LinearOperator ? (Que eu sei que já foi discutido antes, mas não me lembro bem das conclusões.)

Eu acho que a ausência de herança múltipla ou uma construção de linguagem para interfaces (ambas as quais têm sido discutidas) requer que certos conceitos sejam implementados apenas por uma interface 'implícita', ou seja, um conjunto de métodos que precisam ser definidos. Iteradores é um desses conceitos, operadores lineares provavelmente seriam outro que precisa ser especificado por um conjunto de métodos em vez de uma hierarquia de tipo. Seria estranho ter um tipo abstrato LinearOperator e então não ter Matrix como um subtipo dele.

@toivoh Essa é uma consideração importante, razão pela qual sugeri novas funções como row(M,i) e col(M,i) para extrair o vetor ou covetor i th de uma matriz. Essas funções seriam apenas para arrays bidimensionais M e para pessoas interessadas em álgebra matricial. Pode parecer um pouco menos óbvio a princípio do que a indexação do estilo MATLAB, mas, de modo geral, separar conceitualmente a ideia de um vetor e sua dupla / transposição / covetor ajuda a tornar as coisas claras. No caso da mecânica quântica, um campo inteiro saltou para a notação bra-ket de Dirac simplesmente porque essa distinção entre vetor e covetor é tão crítica para vetores complexos, e a notação de Dirac torna isso óbvio. Espero que Julia também faça isso, além de ser uma boa ferramenta para matrizes de armazenamento em dimensões superiores e álgebra linear em dimensões superiores! (Porque somos gananciosos, certo?)

Eu tenho que dizer que uma pessoa que vem com experiência em MATLAB e alguma familiaridade com matrizes principalmente reais (mas não é um fanático por álgebra linear hard-core) pode não perceber a princípio por que o que alguns de nós estamos defendendo é importante, mas eu acredite que é.

Já disse isso antes, mas vou repetir: do meu ponto de vista, temos essa lista de prioridades, com causalidades fluindo para baixo:

(1) Os arrays são fundamentalmente contêineres de armazenamento que todos os usuários do Julia usarão, e queremos a melhor semântica para isso. Regras de estilo APL parecem, para mim, uma solução muito boa. Por outro lado, arrays mínimo-bidimensionais no estilo MATLAB com suposições com índices unidimensionais à direita parecem não naturais, confusos e, como Jutho disse, eles podem até mesmo levar a uma programação desleixada onde você não acompanha adequadamente o dimensão de sua matriz. Eu iria tão longe para dizer que para o vetor v , o código v[i,:] deve gerar um erro, pois v é unidimensional. Operações elementares como + e .* são úteis para qualquer classe de contêiner, não apenas em matrizes ou álgebra multilinear. O único recipiente de armazenamento de concessão que as pessoas fazem para o material abaixo são os pontos extras em .* etc.

(2) A maioria, mas não todos, os usuários de Julia usarão álgebra de matriz, então adicionamos um pouco de açúcar sintático para algumas operações de vetor e matriz, principalmente com o símbolo * (mas podemos ter divisão de matriz, etc). Neste sistema, consideramos os vetores de uma e duas dimensões como vetores de coluna e matrizes, respectivamente. Para um sistema de matriz totalmente funcional, também precisamos de vetores linha (covetores) e uma operação de transposição ' . Um vetor linha é, em todos os sentidos possíveis, um array unidimensional, e eu afirmo que ele deve ser indexado como tal (e certamente não como covec[1,i] !!). Com as regras APL, somos forçados a fazer alguns distinção entre vetores e covetores, e o sistema de tipos é ideal para isso (lembre-se de que tivemos sorte de já podermos reutilizar a matriz e o vetor como tipos de arrays uni e bidimensionais em vez de um tipo de invólucro ... em princípio, poderíamos embrulhe-os também, mas não vejo por que). Com o sistema de tipos, o compilador pode descobrir que CoVector * Vector é escalar e Vector * CoVector é uma matriz e assim por diante. Como toivoh disse, _do ponto de vista do MATLAB, o CoVector está exatamente acompanhando a primeira dimensão "ausente". _ Usuários casuais não precisarão construir esses objetos; eles apenas inserirão vetores e matrizes e usarão as operações * e ' . Pessoas que se importam notarão e apreciarão a distinção. A mudança _maior_ para os usuários é a necessidade de mudar M[i,:] para usar uma nova função row(M,i) ou converter para a classe wrapper com algo como Transpose(M[i,:]) ou M[i,:]' - pode-se pensar nisso como uma vantagem, porque, como a notação de Dirac, você nunca esquece quais objetos são vetores e quais são covetores, e atribuir a objetos com afirmações de tipo levantará erros quando apropriado. Mas acho que vale a pena debater essa notação. No futuro, podemos até estender o sistema de embalagens para permitir uma avaliação eficiente com atraso. É uma vitória para todos, tanto quanto posso ver.

(3) Alguns de nós estão interessados ​​em álgebra multilinear e tiveram que aprender a diferença entre um espaço dual e transposição, etc. Jutho tocou nisso. Os arrays de Julia já são ótimos dispositivos de armazenamento para tensores de dimensões superiores, e pacotes adicionais (que podem ou não encontrar seu caminho para o Base) serão usados ​​por pessoas como nós. Não precisamos, nem queremos operações como ' ou * definidas em arrays de dimensionalidade maior que dois. Não consigo ver um caso de uso orientado ao armazenamento para ' que não possa ser feito de forma mais limpa reordenando explicitamente os índices. A ideia de seguir índices unidimensionais apenas torna o conceito menos atraente ... então, por favor, mantenha ' para matrizes unidimensionais e bidimensionais - como o MATLAB :) (veja, eu tenho coisas legais para dizer sobre MATLAB ...)

Para um sistema matricial totalmente funcional, também precisamos de vetores linha (covetores) e uma operação de transposição '.

Essa afirmação realmente atinge o cerne do problema. Indexação, transposição e * produtos são todos misturados. Além disso, não é possível reconciliar a semântica completa dos vetores de linha com a dos covetores sem introduzir uma função como row(A, i) para retornar a i th linha de A como um covector em vez de uma matriz unidimensional. Atualmente A[1, :] quer significar "pegar a primeira linha de A" e "pegar a primeira fatia ao longo da segunda dimensão de A", mas essas noções são fundamentalmente incompatíveis na proposta do covetor.

Um vetor linha é, em todos os sentidos possíveis, uma matriz unidimensional, e eu afirmo que ele deve ser indexado como tal.

Eu acredito que você está sendo um pouco superficial com esta declaração. A discussão anterior estabeleceu claramente que, se você quiser uma semântica de álgebra linear completa (com os produtos e transpostos corretos), um vetor linha não pode ter o mesmo tipo de um array unidimensional. Primeiro, a indexação em um covector deve retornar valores conjugados complexos, (v')[1] = conj(v[1]) , para consistência em relação ao produto interno dot . Em segundo lugar, covetores não são vetores, eles são funcionais lineares esperando para produzir um produto interno com um vetor. Terceiro, vetores e covetores têm diferentes comportamentos de concatenação de array em hcat e vcat . Por todas essas razões, um vetor linha não pode ser "em todos os sentidos possíveis uma matriz unidimensional".

Eu seria a favor de me livrar dos singletons à direita: acho que nunca vi um código de Julia que o utilizasse. Eu ia dizer originalmente que precisaríamos dele para arrays 0-dimensionais, mas vejo que X[] funciona muito bem.

Acho que a indexação APL, junto com a função row(M,i) para vetores de linha, faz mais sentido e parece um bom meio-termo. Não tenho certeza sobre a indexação de vetor de linha, mas _não_ gosto de (v')[1,i] .

A outra grande decisão, que ainda nem tocamos, são os tipos. Minha única descoberta de ter experimentado isso antes é que é realmente difícil ter v' sendo um AbstractVector , pois isso torna o despacho uma bagunça.

Duas opções possíveis:

  1. Usamos tipos separados para matriz e vetor:

    • Transpose <: AbstractMatrix

    • CoVector <: Any

    • algum tipo de transposição para objetos Factorization .

  2. Usamos o mesmo tipo para todas as transposições, mas X' _not_ é um AbstractMatrix

    • Transpose <: Any

Para a conjugação, podemos

uma. definir ConjugateTranspose (junto com ConjugateCoVector se escolhermos a opção 1 acima)

b. use um tipo de invólucro Conjugate e aninhe-se apropriadamente: precisaríamos de alguma convenção para usarmos Transpose{Conjugate{T}} ou Conjugate{Transpose{T}} .

Gosto de não ter Transpose{Matrix} um subtipo de AbstractMatrix . Acho que o análogo mais próximo que temos na base é um tipo de matriz especial como Symmetric , que se comporta algebricamente como uma matriz, mas não em sua semântica de indexação. (# 987 estabeleceu que, sem herança múltipla ou características sagradas, a hierarquia de tipos deve respeitar a semântica do contêiner em relação à semântica algébrica.)

O problema de compor tipos que são basicamente "tags semânticas" também apareceu em # 8240. Acho que Transpose{Conjugate{T}} seria preferível, já que a conjugação é uma noção que cai no campo de elementos subjacente.

Aqui estão alguns exemplos que às vezes seguem a mesma regra do singleton final do MATLAB, e às vezes não:

  • Os singletons à direita são permitidos em operações de indexação. (Como o MATLAB)
julia> (1:5)[5,1,1,1,1,1,1]
5
  • Fatias à direita não são permitidas em operações de indexação. (Ao contrário do MATLAB, que os permite.)
julia> (1:5)[5,:]
ERROR: BoundsError()
 in getindex at abstractarray.jl:451
  • Para matrizes de classificação> = 3, singletons finais implícitos são adicionados a uma operação de atribuição indexada quando há menos índices do que a classificação da matriz e o último índice é um escalar (como MATLAB):
julia> A=zeros(2,2,2); A[1,2]=5; A #Same as A[1,2,1]=5
2x2x2 Array{Float64,3}:
[:, :, 1] =
 0.0  5.0
 0.0  0.0

[:, :, 2] =
 0.0  0.0
 0.0  0.0
  • Para classificação> = 3 matrizes, _slices_ implícitos finais são adicionados a uma operação de atribuição indexada quando há menos índices que a classificação da matriz e o último índice é _não escalar_ (como MATLAB):
julia> A[:,:]=3; A
2x2x2 Array{Float64,3}:
[:, :, 1] =
 3.0  3.0
 3.0  3.0

[:, :, 2] =
 3.0  3.0
 3.0  3.0
  • Para matrizes de classificação> = 3, singletons finais implícitos são adicionados a uma operação de indexação quando há menos índices do que a classificação da matriz e o último índice é um escalar (como MATLAB):
julia> A=reshape(1:8,2,2,2); A[:,1]
2-element Array{Int64,1}:
 1
 2

julia> A[:,1,1]
2-element Array{Int64,1}:
 1
 2
  • Para matrizes de classificação r> = 3, uma operação de indexação quando há k <r índices que a classificação da matriz e o último índice é uma fatia lineariza implicitamente a matriz de classificação rk restante. (como o MATLAB):
julia> A=reshape(1:8,2,2,2); A[1,:]
1x4 Array{Int64,2}:
 1  3  5  7

julia> A=reshape(1:8,2,2,2); A[1,:,:]
1x2x2 Array{Int64,3}:
[:, :, 1] =
 1  3

[:, :, 2] =
 5  7
  • Os singletons à direita não são descartados nas operações de atribuição. (Ao contrário do MATLAB)
julia> A=zeros(1); A[1] = randn(1,1)
ERROR: `convert` has no method matching convert(::Type{Float64}, ::Array{Float64,2})

You might have used a 2d row vector where a 1d column vector was required.
Note the difference between 1d column vector [1,2,3] and 2d row vector [1 2 3].
You can convert to a column vector with the vec() function.
 in setindex! at array.jl:307

julia> A=zeros(1,1); A[1,1] = randn(1)
ERROR: `convert` has no method matching convert(::Type{Float64}, ::Array{Float64,1})
 in setindex! at array.jl:308
  • Julia não anexa automaticamente uma dimensão de singleton à direita ao fazer isso permite uma operação válida. (Ao contrário do Matlab, que faz)
julia> 1/[1.0,] #In MATLAB, interpreted as the inverse of a 1x1 matrix
ERROR: `/` has no method matching /(::Int64, ::Array{Float64,1})
  • Os produtos externos funcionam e são uma exceção à regra anterior; sua semântica usa implicitamente um singleton à direita no primeiro argumento. (Como Matlab)
julia> [1:5]*[1:5]' # Shapes are (5,) and (1,5) - promoting to (5,1) x (1,5) works
5x5 Array{Int64,2}:
 1   2   3   4   5
 2   4   6   8  10
 3   6   9  12  15
 4   8  12  16  20
 5  10  15  20  25

Parece que essas coisas merecem um pouco de limpeza, uma vez que tenhamos concluído como gostaríamos que se comportassem. O comportamento atual ao indexar com poucos índices onde o último é uma fatia parece particularmente duvidoso.

Uau. Os exemplos que Jiahao apresenta são extraordinariamente perturbadores ... Não gosto de rastrear singletons em operações de indexação e operações de atribuição de indexação por causa de sua implicação e ambigüidade. Se você não estiver ciente desses comportamentos com antecedência, poderá acabar fazendo uma coisa quando, na verdade, está tentando fazer outra. Sou a favor de um uso exato e claro da linguagem e de evitar atalhos ambíguos.

Para realmente implementar isso, vamos precisar de algum tipo de despacho triangular, caso contrário, não tenho certeza de como você expressaria coisas como "uma matriz com Complex64 ou Complex128 entradas, sua transposição , ou seu conjugado transpõe ". Especialmente se usarmos as opções 2 + b acima.

@ esd100 , quais destes são ambíguos ou perigosos? Eles são todos convenientes - quando "singletons virtuais" são imaginados para você e você os queria - ou inconvenientes - quando você os queria e eles não eram. Nenhum deles tem dois significados plausíveis com comportamentos diferentes.

Um vetor linha é, em todos os sentidos possíveis, uma matriz unidimensional, e eu afirmo que ele deve ser indexado como tal.

Eu acredito que você está sendo um pouco superficial com esta declaração.

Verdadeiro! Desculpe @jiahao , vou culpar o jetlag. A palavra que pretendia escrever é "tensor" e estava me referindo estritamente ao comportamento da indexação [] . Você está correto ao dizer que a concatenação é uma consideração importante, e acho que a maneira natural de fazer isso (para construir uma matriz) é razoavelmente óbvia (pensando nela como tendo o tamanho de 1 xn neste caso). Claramente, um covector não é "em todos os sentidos" um 1D Julia Array ... esse é o ponto inteiro ...

Os exemplos de jiahao também me perturbam. Eu seria a favor da remoção de qualquer comportamento de rastreio de singleton. Linearizar dimensões posteriores pode ser útil, mas também pode encorajar um tipo de preguiça em que você esquece quantas dimensões seu array tem ... (eu acho que é isso que está implícito em "ambíguo" e "perigoso" ... Eu realmente quero Julia lança um erro quando eu trato um array de 16 dimensões como um array de 15 dimensões, por exemplo).

quais deles são ambíguos ou perigosos?

O quarto parece ambíguo e perigoso para mim.

julia> A=zeros(2,2,2); A[:,:]=3; A
2x2x2 Array{Float64,3}:
[:, :, 1] =
 3.0  3.0
 3.0  3.0

[:, :, 2] =
 3.0  3.0
 3.0  3.0

Claramente (para mim) isso deve ser um erro de sintaxe. A é classificado como 3 e a 3ª dimensão não foi referenciada. É mais provável que a terceira dimensão tenha sido deixada de lado por erro e um bug difícil de encontrar foi introduzido no código. Eu ficaria grato por obter um erro neste ponto (como ocorre em IDL e fortran).

Acho que é melhor se os arrays permitirem apenas a indexação com o número correto de índices ou o caso especial de indexação linear 1D (uma vez que é extremamente útil). Isso incluiria a proibição de singletons à direita também.

ou o caso especial de indexação linear 1D (uma vez que é extremamente útil)

Com que facilidade nossos princípios são vendidos! :)

Nunca gostei tanto de indexação linear; parece-me um pouco hack. Freqüentemente, queremos apenas a maneira mais rápida de iterar em um array. E para todos, exceto matrizes densas simples, a indexação linear pode ser muito lenta. Dito isso, podemos não ser capazes de eliminar totalmente a indexação linear (por motivos de desempenho e por quebrar muito código).

Sim, é verdade. Mas pelo menos a indexação 1D é visualmente distinta. Já a indexação de um array 6D com 5D é muito menos. Em qualquer caso, eu preferiria ter regras de indexação mais rígidas e desistir da indexação linear 1D do que ir na outra direção. Especialmente porque é possível obter facilmente uma referência remodelada para o array que compartilha memória.

Podemos usar colchetes {} (ou algum outro) para indexação linear e manter [] para indexação multidimensional? Apenas uma ideia...

Em 23 de março de 2015, às 14h36, Bob Portmann [email protected] escreveu:

Sim, é verdade. Mas pelo menos a indexação 1D é visualmente distinta. Já a indexação de um array 6D com 5D é muito menos. Em qualquer caso, eu preferiria ter regras de indexação mais rígidas e desistir da indexação linear 1D do que ir na outra direção. Especialmente porque é possível obter facilmente uma referência remodelada para o array que compartilha memória.

-
Responda a este e-mail diretamente ou visualize-o no GitHub https://github.com/JuliaLang/julia/issues/4774#issuecomment -84805310.

Eu também acho que seria bom se pudéssemos separar a sintaxe
para indexação linear a partir da sintaxe de indexação regular, mas concordo que
pode ser uma mudança extremamente significativa.

Embora esse problema pareça atrair muito debate, a maior parte do trabalho real para melhorar a indexação aconteceu em um grande número de solicitações pull durante o ciclo de 0,4. Por exemplo, agora que temos CartesianIndex e amigos, não vejo por que seria necessário separar a sintaxe para indexação linear e cartesiana --- na verdade, agora você pode combiná-los (# 10524). Eu gostaria de me livrar da indexação linear também, mas às vezes ela tem mais desempenho (provavelmente devido a # 9080; enumerate e zip sofrem do mesmo problema). Provavelmente devemos implementar fastindex como um invólucro em torno de eachindex , como em # 10507.

Se você estiver interessado em regras de indexação, em vez de martelar esse problema até a morte, vamos nos concentrar na fronteira mais interessante. Neste momento, é sem dúvida o # 10525. Em particular https://github.com/JuliaLang/julia/pull/10525#issuecomment -84597488 precisa de algum tipo de resolução.

@timholy Eu realmente não acompanhei todos os desenvolvimentos na indexação cartesiana rápida e, tendo acabado de dar uma olhada em base / multidimensional.jl, vejo que há muita metaprogramação acontecendo. Existe alguma chance de você ter tempo para escrever (ou dar uma palestra na JuliaCon) sobre como tudo isso funciona?

De certa forma, não há muito a saber: embora haja muita coisa acontecendo por baixo do capô, a ideia é torná-lo muito simples de usar. Tão literalmente

k = 0
for I in eachindex(A)
     B[k+=1] = A[I]   # B is being linearly-indexed, A is being cartesian-indexed
end

pode ser tudo que você precisa saber. (Em outras palavras, a ajuda em eachindex pode ser toda a documentação necessária.) No entanto, acontece que você pode escrever um grande número de algoritmos usando algumas extensões deste paradigma básico; o que são essas extensões e por que são poderosas pode, de fato, ser menos óbvio.

Pretendo escrever isso nos próximos meses, mas se as pessoas realmente quiserem saber os detalhes, talvez uma palestra na JuliaCon seja razoável. @Jutho ou @mbauman poderiam dar essa palestra tão bem quanto eu.

Eu acrescentaria mais sobre ambigüidade, no entanto, acho que algumas das discussões posteriores resumiram certos pontos-chave. Talvez eu seja mais sensível a esse tipo de ambigüidade como um estranho ou por medo, correndo o risco de soar dramático demais. Em minha humilde opinião, qualquer número de exercícios simples de reflexão de missão crítica pode levar você a um caminho em que a análise de custo / benefício de um simples erro não vale a pena.

Provavelmente devemos implementar fastindex como um wrapper em torno de cada índice, como em # 10507

@timholy Existe alguma razão para que eachindex retorne um UnitRange para matrizes lineares rápidas? Ele ainda seria de tipo estável, e se o chamador quiser garantir que receberá um CartesianIndex, ele pode construir manualmente um CartesianRange (também podemos adicionar um método para eachindex(size(A)) já que CartesianRange não é exportado ).

Foi o que aconteceu no início, mas acho que @Jutho mudou. (Se isso não for verdade, então provavelmente eu mudei sem perceber.) Presumo que a mudança foi motivada por uma questão de consistência (então você pode contar com CartesianIndex ), o que tem um certo sentido para isso . Mas, como você assinalou, existem meios alternativos para garantir isso.

@Jutho , alguma

Não me lembro de mudar eachindex , mas pode ser. Certamente não me lembro de um bom motivo para isso, além de ter um resultado consistente independente do tipo de array. Mas se a diferença entre arrays com e sem indexação linear eficiente for claramente explicada nos documentos, não vejo razão para eachindex não retornar um intervalo linear no caso de arrays com indexação linear eficiente.

@timholy , em resposta ao seu post anterior, não posso comparecer à JuliaCon, então sinta-se à vontade para falar sobre o assunto de CartesianIndex (de qualquer forma, só contribuí com alguns toques finais). Já estou ansioso pelos relatórios e vídeos da conferência.

Um pensamento caprichoso: para um array A de qualquer dimensão (incluindo dimensão> 2), pode-se ter A' permutando ciclicamente os índices de A . Portanto, A'[i, j, k] == A[j, k, i] . Isso se reduziria à transposta de matriz usual para matrizes 2d, bem como à transposta usual para "vetores" de linha e coluna quando interpretados como no MATLAB (ou seja, como [n, 1] e [1, n] matrizes 2d respectivamente). Portanto, ele nunca mapearia um "vetor" de 2ª coluna para um covetor verdadeiro ou um "vetor" de 2d linha para um vetor verdadeiro. (Eu acho que essa propriedade é boa, mas outros podem discordar.) Ela forneceria as identidades A'' == A' e v'' == v _para matrizes e vetores interpretados como objetos de matriz_. Dado o tipo de hierarquia de tipo conforme discutido acima (onde vetores e covetores verdadeiros são suficientemente distintos dos arrays abstratos), ' ainda poderia receber um método totalmente diferente para vetores e covetores verdadeiros, onde corresponde ao conceito algébrico linear e não precisa satisfazer v'' == v (mas poderia se fosse o que as pessoas decidissem que elas queriam).

Para ficar claro: nem por um segundo acho que ' _necessidade_ ter qualquer método para matrizes de dimensão> 2, mas não tinha visto essa proposta acima (a menos que seja isso o que significa "inverter índices ") e pensei em mencioná-lo. O maior dano que vejo causar (prima facie) é conceitual: fundir uma operação combinatória (ligeiramente arbitrária) com um operador normalmente considerado do domínio da álgebra linear. A isso se pode responder que pelo menos algum grau de tal combinação é inevitável quando tentamos estender os conceitos algébricos lineares em conceitos puramente centrados em dados (como evidenciado por toda esta discussão). Como tal, podemos também colher uma abreviatura conveniente para permutações cíclicas de matrizes multidimensionais como um subproduto da determinação do que ' deve fazer em geral para dimensões de matriz multidimensional, desde que se reduza ao caso esperado em 2 dimensões. Pode-se até adicionar um método que leva um argumento inteiro de forma que A'(k)[I] permute ciclicamente os índices de A[I] k vezes, dando A'(ndims(A))[I] == A[I] e A'(-k)[I] permuta os índices na direcção oposta.

Apenas um pensamento.

Se transpose for definido para arrays multidimensionais, eu preferiria que ainda satisfizesse A''=A , ou seja, é seu próprio inverso. Isso está em conflito direto com a proposta anterior.

Isso é justo. Como eu disse, minha sugestão só é (potencialmente) atraente se alguém estiver confortável em deixar uma brecha crescer entre o significado algébrico linear de transpor e qualquer significado centrado na matriz que alguém impõe no caso d> 2. Meu raciocínio era que, contanto que essa fenda já esteja (meio que) acontecendo, e se os métodos simplesmente não fossem usados ​​de outra forma, poderia ser legal ter uma abreviação para permutar índices - contanto que não exija nenhum tratamento especial para fazer o caso d = 2 funcionar. Como você (@Jutho) mencionou, é uma espécie de coincidência conveniente que transpor dimensões no caso 2d tenha o significado algébrico linear que tem (e somente após identificar (co) vetores como matrizes 2d, por falar nisso), então talvez não não é preciso ser exigente com relação às propriedades matemáticas de transpose (por exemplo, exigir A'' == A ) para d> 2. Se alguém deseja seguir esse caminho, há muitos métodos potencialmente úteis que poderiam ser atribuído, por exemplo: A' permuta ciclicamente uma vez, A'(k) ciclicamente permuta k vezes, A'(I) para I::Array{Int, 1} com comprimento <= ndims(A) ciclicamente permuta os índices listados em I , e A'(p) para p::Permutation de comprimento <= ndims(A) permuta índices de acordo com p . Mas suponho que talvez a melhor coisa a fazer seja fazer um pacote e ver se é útil o suficiente para pegar.

Apoio duas alterações / esclarecimentos que foram mencionados, por exemplo, por StefanKarpinski em 16 de outubro de 2014 e simonbyrne em 22 de março de 2015, mas com uma estipulação:

  1. transpose só deve ser usado para vetores e matrizes bidimensionais, não para tensores gerais.
  2. Os operadores * e transpose devem excluir as dimensões do singleton à direita no final do cálculo. Caso contrário, as dimensões do singleton à direita podem ser preservadas.

Essas duas mudanças forneceriam muitas conveniências e resolveriam muitas ambigüidades no trabalho tradicional de álgebra linear. Eles intencionalmente não dizem nada sobre a indexação de array geral ou álgebra de tensores, que pode ser considerada separadamente. (Em particular, a transposição de tensor deve ser considerada uma operação totalmente separada da transposição de matriz / vetor.)

Sob esta proposta, ao trabalhar com multiplicação e transposição de matrizes, não haveria essencialmente nenhuma distinção entre um vetor e uma matriz de uma coluna, nem haveria quaisquer distinções significativas entre um escalar, um vetor de comprimento 1 e um 1-by -1 matrix. No entanto, x' seria uma matriz 1 por n, não um vetor.

Por outro lado, com o objetivo de armazenar dados, uma matriz de qualquer tamanho pode ser construída. Nenhuma dimensão singleton seria excluída porque os conceitos da álgebra linear de multiplicação e transposição não estariam envolvidos.

Abaixo está uma transcrição hipotética de uma sessão de Julia com as mudanças propostas.

Primeiro, definimos um escalar, dois vetores e uma matriz.

julia> alpha = 2.0
2.0

julia> x = [1.0; 2.0; 3.0]
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> y = [4.0; 5.0; 6.0]
3-element Array{Float64,1}:
 4.0
 5.0
 6.0

julia> A = [1.0 2.0 3.0; 4.0 5.0 6.0; 7.0 8.0 9.0]
3x3 Array{Float64,2}:
 1.0  2.0  3.0
 4.0  5.0  6.0
 7.0  8.0  9.0

A multiplicação escalar vetorial funciona mesmo se dimensões estranhas estiverem presentes, e o resultado é sempre um vetor.

julia> alpha*x
3-element Array{Float64,1}:
 2.0
 4.0
 6.0

julia> alpha*x[:,[1]]
3-element Array{Float64,1}:
 2.0
 4.0
 6.0

Transpor é uma involução.

julia> x'
1x3 Array{Float64,2}:
 1.0  2.0  3.0

julia> x''
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

julia> x==x''
true

julia> x'''
1x3 Array{Float64,2}:
 1.0  2.0  3.0

Multiplicar uma matriz por uma matriz de uma coluna produz um resultado idêntico ao produto matriz-vetor.

julia> A*x
3-element Array{Float64,1}:
 14.0
 32.0
 50.0

julia> A*x[:,[1]]
3-element Array{Float64,1}:
 14.0
 32.0
 50.0

Uma matriz de uma linha vezes uma matriz é igual a uma matriz de uma linha.

julia> x'*A
1x3 Array{Float64,2}:
 30.0  36.0  42.0

O produto interno é um escalar e é produzido pelas regras mais gerais para produtos matriz-vetor e matriz-matriz.

julia> x'*y
32.0

julia> x'*y[:,[1]]
32.0

O produto externo não é nada especial.

julia> x*y'
3x3 Array{Float64,2}:
  4.0   5.0   6.0
  8.0  10.0  12.0
 12.0  15.0  18.0

julia> x[:,[1]]*y'
3x3 Array{Float64,2}:
  4.0   5.0   6.0
  8.0  10.0  12.0
 12.0  15.0  18.0

Um vetor vezes um vetor é um erro.

julia> x*y
ERROR: `*` has no method matching *(::Array{Float64,1}, ::Array{Float64,1})

Um vetor vezes uma matriz é um erro.

julia> x*A
ERROR: DimensionMismatch("*")
 in gemm_wrapper! at linalg/matmul.jl:270
 in * at linalg/matmul.jl:74

A multiplicação da matriz é associativa.

julia> (x*x')*y
3-element Array{Float64,1}:
 32.0
 64.0
 96.0

julia> x'*y
32.0

julia> x*(x'*y)
3-element Array{Float64,1}:
 32.0
 64.0
 96.0

julia> norm((x*x')*y-x*(x'*y))
0.0

EDIT: Excluídos dois exemplos envolvendo rebaixamento de dimensão interna em um produto. Eles não tiveram muita influência na discussão atual.

Temo que isso perderia a estabilidade do tipo das operações envolvidas, visto que descartar as dimensões do singleton à direita não é uma operação do tipo estável.

A meu ver, todo o negócio do covector é ter uma dimensão singleton que é conhecida com base no tipo que será removido. Além disso, temos tentado bastante preservar a distinção entre um vetor e uma matriz que por acaso tem uma coluna, mas essa sugestão removeria essa distinção em alguns casos, como x'' .

Sugestão herética: e se retirássemos a dimensionalidade dos parâmetros de tipo e fizéssemos todas as verificações de dimensionalidade / tamanho no tempo de execução? (Acabamos fazendo uma boa quantidade disso de qualquer maneira.) E deixe o parâmetro de dimensionalidade para tipos que também codificam o tamanho como um parâmetro de tipo. (Fica escondido por duas semanas e muda sua conta no github para @SomeoneWhoCertainlyIsntTimHolyUhUhNoWay.)

(Claro, eu realmente reclamaria de mim mesmo por conta do # 10525 sozinho.)

FWIW, eu diria que não é uma ideia tão maluca: é como o Torch funciona, por exemplo, e os desenvolvedores do Torch expressaram infelicidade com a codificação da dimensionalidade de Julia dentro do sistema de tipos.

@timholy Pergunta: podemos usar isso para melhorar a semântica em relação à álgebra linear e as transposições de vetores?

Se alguém ainda estivesse interessado em usar CoVector / DualVector / TransposeVector eles ainda teriam que embrulhar nosso novo TimHolyArray{DataType} _and_ e teríamos que entender a transposição de uma matriz de dimensão maior que dois (ou um), ou então proibir a construção TransposeVector(tharray) quando tharray tiver dimensão maior que dois (ou um) ... Na verdade, todos os tipos de coisas terão que dar erros no nível de tempo de execução que são atualmente erros de tempo de compilação (como multiplicações que estão atualmente indefinidas e, portanto, proibidas pelo sistema de tipos).

Por outro lado, implementar um sinalizador de transposição dentro dessa nova classe pode ser ruim ... adiciona mais complexidade ao que deveria ser um contêiner eficiente e leve. Eu tenderia a descartar essa opção e deixar o trabalho duro para o sistema de compilador / tipo.

Não sou necessariamente contra a sua ideia - mas parece ser uma questão adicional que poderia ser feita em paralelo às transposições vetoriais.

@timholy : Tenho certeza se esse é o caminho a seguir ou não. Há situações em que acho muito útil despachar na dimensão.

A sugestão era fazer com que isso se aplicasse a todos os arrays. Mas estou contra minha própria proposta agora, simplesmente porque para muitos casos multidimensionais você precisa gerar N loops para N arrays dimensionais. Não poderíamos mais fazer isso se N não fosse um parâmetro de tipo.

Sim, não é todo esse o ponto de suas macros cartesianas (ou a função testada que ainda não estou acostumado :-))?
Fiz coisas semelhantes em C ++, onde é muito difícil ter a dimensão como um parâmetro de modelo. Mas tive situações em que a variante dinâmica era limitante porque era necessário grandes instruções if para especializar as diferentes dimensões do array.

Proposta:

  1. Renomeie o comportamento de multiplicação da matriz atual para timesfast e o comportamento de transposição atual para transposefast .
  2. Modifique (*) e transpose para truncar as dimensões do singleton à direita como no comentário anterior . Por exemplo, u'*v se tornaria um escalar, v'' se tornaria um vetor e (v*v')/(v'*v) seria definido.

O comportamento existente é de tipo estável. O comportamento proposto segue as convenções de muitos textos de álgebra linear. Ambos são valiosos. Talvez Julia devesse ter os dois.

Quero usar Julia na sala de aula, então voto no comportamento padrão para valorizar a conveniência em vez da eficiência.

Agradeço a vários contribuidores por me informarem sobre este tópico tão longo!

@briansutton : Acho que você realmente deveria reconsiderar o que está pedindo. Eu o encorajaria a esperar até ter uma compreensão mais profunda de como Julia funciona antes de propor que redefinamos o significado da multiplicação.

Esse problema tem a ver com como podemos evitar o maior número possível de casos especiais.

As regras que permitem falsificar as dimensões do singleton quebram no momento em que uma das dimensões do singleton é aquela com a qual você realmente se importa. Com a regra "truncar [todas] dimensões de singleton à direita", se v é um vetor de 1 elemento, v' é um escalar e, portanto, v'' também é um escalar. Portanto, mesmo nesta proposta, a propriedade v == v'' nem sempre é válida.

Você pode tentar modificar a regra para "truncar apenas a última dimensão do singleton à direita se a matriz tiver dimensionalidade 2". Mas mesmo com esta regra modificada, o produto externo v * w' não segue automaticamente da definição de multiplicação de matriz, mas em vez disso, deve ser sua própria definição especial de Vector * Matrix , e a definição deve ser "lançar um erro a menos que as formas sejam (N) x (1, M)".

@jiahao :

Ao fazer álgebra linear com vetores e matrizes, não quero fazer nenhuma distinção entre um escalar, um vetor de 1 elemento e uma matriz 1 por 1. Atualmente, várias operações produzem qualquer um dos três objetos. Minha sugestão é, para as operações de multiplicação e transposição de matrizes, escolher uma das três para ser a representação preferida.

Com relação ao seu primeiro exemplo ( v==v'' ), eu gostaria de evitar construir um vetor de 1 elemento v em primeiro lugar.

Em relação ao seu segundo exemplo, sim, acho que v*w' deve ser tratado como você descreve. Ao trabalhar com multiplicação e transposição de matrizes, quero que o vetor v e a matriz N por 1 v[:,[1]] denotem o mesmo objeto matemático, mesmo que tenham diferentes representações internas. Isso exigiria um código especial para lidar com o vetor N vezes a matriz 1 por M.

Acho que você ainda não está convencido de que a estabilidade do tipo é importante.

@briansutton : Acho que você acharia muito informativo tentar implementar o mecanismo necessário para fazer sua proposta rodar tão rápido quanto o sistema de tipos atual permite que o código de Julia seja executado. Eu, por exemplo, acredito que seus objetivos declarados são inatingíveis, dados os objetivos Julian de fornecer desempenho previsível e compatibilidade de layout de memória C.

Não tenho certeza se entendo os argumentos que vão e vêm, no entanto, uma coisa chamou minha atenção. Talvez seja ilusão, mas seria bom se o computador fosse capaz de pensar tão rapidamente quanto o cérebro humano. O que quero dizer é que, quando Brian Sutton fala sobre o programa "escolhendo uma representação preferida", imagino um programa que pode pensar, assim como nós, quando fazemos matemática. Talvez isso não seja viável com a tecnologia atual e vá desacelerar muito as coisas. Mas, não seria bom ...

Eu sou um físico e um usuário ativo de Julia.

Eu não tenho mais uma forte preferência em manter o clima ou eliminar todas as dimensões do singleton

Mas aqui eu gostaria de levantar uma questão intimamente relacionada.

A implementação atual de Julia:

Seja V um tensor 3-dim rank-1 (um vetor)
V [1] nos dará um escalonador, não um tensor 1-dim rank-1

Seja A um tensor 3x4x5 rank-3
B = A [1,:,:] nos dará um tensor de classificação 3 de 1x4x5.

Os dois comportamentos acima não são muito consistentes.

Eu prefiro fortemente o seguinte recurso de indexação / deslizamento:

Seja A um tensor 3x4x5 rank-3
B = A [1,:,:] nos dará um tensor de classificação 2 4x5.
C = A [1: 1,:,:] nos dará um tensor de classificação 2 de 1x4x5.
(atualmente, os dois acima fornecem o mesmo resultado)

Seja V um tensor de categoria 1
B = V [1] nos dará um escalonador
C = V [1: 1] nos dará um tensor 1-dim rank-1.

Este recurso nos ajudará a mudar a forma do tensor mais facilmente e será útil quando permitirmos índices singleton à direita.

Melhor

Xiao-Gang


De: esd100 [notificaçõ[email protected]]
Enviado: terça-feira, 9 de junho de 2015, 21:46
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Assunto: Re: [julia] Levando a sério as transposições de vetor (# 4774)

Não tenho certeza se entendo os argumentos que vão e vêm, no entanto, uma coisa chamou minha atenção. Talvez seja ilusão, mas seria bom se o computador fosse capaz de pensar tão rapidamente quanto o cérebro humano. O que quero dizer é que, quando Brian Sutton fala sobre o programa "escolhendo uma representação preferida", imagino um programa que pode pensar, assim como nós, quando fazemos matemática. Talvez isso não seja viável com a tecnologia atual e vá desacelerar muito as coisas. Mas, não seria bom ...

-
Responda diretamente a este e-mail ou visualize-o em Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

Quero dizer: C = A [1: 1,:,:] nos dará um tensor de classificação 3 de 1x4x5.

Xiao-Gang


De: Xiao-Gang Wen [[email protected]]
Enviado: segunda-feira, 22 de junho de 2015, 12:01
Para: JuliaLang / julia; JuliaLang / julia
Cc: Xiao-Gang Wen
Assunto: RE: [julia] Levando a sério as transposições de vetor (# 4774)

Eu sou um físico e um usuário ativo de Julia.

Eu não tenho mais uma forte preferência em manter o clima ou eliminar todas as dimensões do singleton

Mas aqui eu gostaria de levantar uma questão intimamente relacionada.

A implementação atual de Julia:

Seja V um tensor 3-dim rank-1 (um vetor)
V [1] nos dará um escalonador, não um tensor 1-dim rank-1

Seja A um tensor 3x4x5 rank-3
B = A [1,:,:] nos dará um tensor de classificação 3 de 1x4x5.

Os dois comportamentos acima não são muito consistentes.

Eu prefiro fortemente o seguinte recurso de indexação / deslizamento:

Seja A um tensor 3x4x5 rank-3
B = A [1,:,:] nos dará um tensor de classificação 2 4x5.
C = A [1: 1,:,:] nos dará um tensor de classificação 2 de 1x4x5.
(atualmente, os dois acima fornecem o mesmo resultado)

Seja V um tensor de categoria 1
B = V [1] nos dará um escalonador
C = V [1: 1] nos dará um tensor 1-dim rank-1.

Este recurso nos ajudará a mudar a forma do tensor mais facilmente e será útil quando permitirmos índices singleton à direita.

Melhor

Xiao-Gang


De: esd100 [notificaçõ[email protected]]
Enviado: terça-feira, 9 de junho de 2015, 21:46
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Assunto: Re: [julia] Levando a sério as transposições de vetor (# 4774)

Não tenho certeza se entendo os argumentos que vão e vêm, no entanto, uma coisa chamou minha atenção. Talvez seja ilusão, mas seria bom se o computador fosse capaz de pensar tão rapidamente quanto o cérebro humano. O que quero dizer é que, quando Brian Sutton fala sobre o programa "escolhendo uma representação preferida", imagino um programa que pode pensar, assim como nós, quando fazemos matemática. Talvez isso não seja viável com a tecnologia atual e vá desacelerar muito as coisas. Mas, não seria bom ...

-
Responda diretamente a este e-mail ou visualize-o em Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -110554622.

O que você está perguntando é como slice funciona atualmente. Minha sensação é que A[stuff] se tornará um sinônimo para slice(A, stuff) e, portanto, provavelmente você realizará seu desejo.

Caro Tim:

Obrigado pela dica. Eu tentei fatia. Não atende às minhas necessidades. O Slice produz um novo tipo de dados "subarray" que não posso usar em meu outro código que usa o tipo de dados :: Array.

Talvez eu possa alterar meu outro código para que eles permitam o tipo de dados "subarray".

Xiao-Gang


De: Tim Holy [notificaçõ[email protected]]
Enviado: segunda-feira, 22 de junho de 2015 17:32
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Assunto: Re: [julia] Levando a sério as transposições de vetor (# 4774)

O que você está perguntando é como o slice atualmente funciona. Minha sensação é que A [coisas] se tornará sinônimo de fatia (A, coisas) e, portanto, você provavelmente realizará seu desejo.

-
Responda a este e-mail diretamente ou visualize-o em Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -114268796.

Existe algo realmente dependente do uso dos tipos Array concretos em vez de AbstractArray em seu código? Pode exigir nada mais do que uma busca / substituição de Array por AbstractArray para fazer as coisas funcionarem.

Querido Scott

Muito obrigado pela dica.

Xiao-Gang


De: Scott P. Jones [notificaçõ[email protected]]
Enviado: quinta-feira, 25 de junho de 2015 9:55
Para: JuliaLang / julia
Cc: Xiao-Gang Wen
Assunto: Re: [julia] Levando a sério as transposições de vetor (# 4774)

Existe algo realmente dependente do uso de tipos concretos de Array em vez de AbstractArray em seu código? Pode exigir nada mais do que uma pesquisa / substituição de Array por AbstractArray para fazer as coisas funcionarem.

-
Responda diretamente a este e-mail ou visualize-o em Gi tHubhttps: //github.com/JuliaLang/julia/issues/4774#issuecomment -115265047.

Isso funcionou para você? Fico feliz em ajudar!

@wenxgwen , alternativamente, você poderia chamar copy(slice(A,...)) para obter uma cópia da fatia em um novo Array que deve funcionar nativamente com suas funções já existentes.

Corrigir esse problema agora se tornou um empreendimento lucrativo

Já que corrigir esse problema agora é crítico em meu caminho para as 3 vírgulas ...
image

Mas com toda a seriedade. Tentei ler a discussão completamente, mas não posso garantir que isso não tenha sido sugerido antes:

Podemos expandir a definição AbstractArray para ter uma característica adicional (semelhante a LinearIndexing?) Que define se os dados subjacentes são baseados em linha ou em coluna de armazenamento? Essa adição abriria as seguintes propriedades (por favor, não se concentre em nomes ... apenas conceitos):

v --> length-2 Vector{Col}
  [ 1
    2 ]

v'  --> length-2 Vector{Row}
  [ 1 2 ]

m --> 2x2 Matrix{Col}
  [ 1 3 
    2 4 ]

m' --> 2x2 Matrix{Row}
  [ 1 2 
    3 4 ]

Some operations:
v'  --> length-2 Vector{Col}
v'' == v
v*v or v'*v'  --> either error, or do element-wise multiplication
v' * v --> scalar
v * v' --> 2x2 Matrix  (could be Row or Col??)
v' * m --> 2-length Vector{Row}
v * m --> either error or broadcasting operation
m * v --> either error or broadcasting operation
m * v' --> 2-length Vector{Col}

Indexing:
v[2] --> 2
v[1,2] --> error
v'[1,2] --> 2
m[1,2]  --> 3
m'[1,2]  --> 2

Size:
length(v)  --> 2
length(v')  --> 2
size(v)  --> (2,)
size(v')  --> (2,)
length(m)  --> 4
size(m)  --> (2,2)

Obviamente, esta proposta está faltando muitas definições, mas talvez ela possa iniciar a discussão. Podemos ter suporte para armazenamento de índice de coluna e índice de linha ao mesmo tempo e "corrigir" alguns problemas ao longo do caminho?

Eu gostaria muito que Julia fosse um armazenamento baseado em linha, já que é naturalmente como eu penso sobre loops e muitas outras operações. (e acho que não sou o único que pensa assim) Comentários, por favor!

É um pensamento interessante, tendo o construtor também levando em linha ou coluna. Acho que já foi discutido antes em algum lugar no sistema de problemas do GitHub. Eu poderia estar errado!

Pessoalmente, prefiro o armazenamento baseado em colunas porque a maioria dos meus livros didáticos usa colunas para matemática e, em Julia, não preciso mudar tudo para usar linhas. Eu também achei estranho no início, mas rapidamente se tornou um problema para o meu trabalho. Existem algoritmos que são mais fáceis de expressar em linhas, no entanto, é por isso que pode ser bom ter a capacidade de usar um armazenamento não padrão quando necessário. Eu espero que a convenção seja sempre retornar uma matriz de coluna principal ou vetor de coluna quando você tem uma função que é exportada de forma que nunca haja uma dúvida sobre qual tipo você tem ao chamar uma função. Caso contrário, ele pode ficar muito bagunçado e se tornar desagradável de usar quando você tiver que procurar sempre que tipo é retornado.

Baseado em coluna versus baseado em linha não está dentro do escopo deste problema.

@tbreloff Gosto muito da ideia. Seria ótimo poder interagir mais facilmente com linguagens / bibliotecas de linha principal.

Jiahao está certo ao dizer que preferir a linha principal versus a coluna principal está fora de questão. Está
apenas um efeito colateral legal que resolve o problema da transposição de uma forma "juliana"
(tipos paramétricos e funções em estágios) dá às pessoas mais flexibilidade em
formato de armazenamento.

Se você não gosta da ideia de adicionar uma característica de linha / coluna aos arrays, então o
exatamente a mesma coisa pode ser realizada com um TransposeView {T, N}, mas eu
suspeito que será mais complicado de implementar bem.

A maior desvantagem do traço adicional é a confusão para novos usuários,
e isso é algo que tenho dificuldade em conciliar.

No sábado, 26 de setembro de 2015, Scott P. Jones [email protected]
escrevi:

@tbreloff https://github.com/tbreloff Gosto muito da ideia. isto
seria ótimo poder interagir mais facilmente com linguagens / bibliotecas
que são linha principal.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment -143436947.

A sobrecarga de chamadas altera o espaço do projeto? Às vezes, no acima, parecia haver uma espécie de ambigüidade no significado de * . Em particular, quando queremos que v::Covector * w::Vector retorne um escalar, estamos considerando * realmente ser "mapa w sob v " em vez de multiplicação de matriz? Em caso afirmativo, então não poderia alguém similarmente exigir que w::Vector * v::Covector retorne um escalar, uma vez que os vetores são mapas lineares sobre covetores?

Talvez seja útil em vez disso sobrecarregar call e escrever v(w) para denotar a operação "mapa sob v " em w e da mesma forma para w(v) - ambos retornariam escalares. Isso permitiria mais margem de manobra na acomodação da semântica que as operações de array e operações algébricas lineares compartilham no caso de arrays 2d?

Em caso afirmativo, então não poderia alguém similarmente exigir que w :: Vector * v :: Covector retorne um escalar, uma vez que os vetores são mapas lineares sobre covetores?

Acho que estamos tentando seguir as convenções de matriz e vetor que muitas pessoas veem na universidade do primeiro ano, que é não comutativa e tem vetor * vetor transposto -> matriz (de posto-1, ou seja, tendo apenas um (diferente de zero) singular valor). O que você escreveu soa mais como um conceito abstrato de álgebra linear, onde o vetor e co / vetores duais são mapas um do outro para algum escalar, o que é bom, mas um pouco diferente do que (IMHO) é tentado em Julia e MATLAB. Acho que poderíamos interpretar o que você escreveu como o produto interno, dot() ou atuando em dois covetores (que _devemos_ provavelmente definir para covetores, eu acho), mas às vezes também queremos o externo -produto, e atualmente o não cumulativo * nos permite escrever e expressar ambos os comportamentos.

Quanto à convenção de chamada v(w) , poderíamos igualmente dizer para o produto escalar que queremos a quantia de v na direção de w que sugere que usemos o operador de indexação v[w] . (Aliás, não estou dizendo que essa é uma boa opção de design de linguagem - apenas uma observação!)

poderíamos igualmente dizer para o produto escalar que queremos a quantidade de v na direção de w que sugere que usemos o operador de indexação v[w] .

Essa sugestão quase, mas não está, em conformidade com a semântica da indexação. Também entra em conflito com nosso suporte atual para indexação de vetor se v for Vector{<:Integer} .

Considere que para v :: Vector{T<:Real} , v[1] é equivalente ao produto escalar v⋅e₁ , onde e₁ é o vetor de base canônica ao longo do primeiro eixo. Portanto, a indexação simples é realmente a função

   v[n] : n :: Integer --> y = (v ⋅ eₙ) :: T

Para indexação vetorial, v[[1, 2]] produz [v⋅e₁, v⋅e₂] que é o resultado da projeção de v no subespaço estendido por {e₁, e₂} , ou equivalentemente é o resultado de v' * [e₁ e₂] .

Portanto, a indexação vetorial é a função

   v[I] : I :: Vector{<:Integer} --> y = v' * [eₙ for n in I] :: Vector{T}

A proposta de fazer v[w] = (w ⋅ v) v é inconsistente com esta definição porque elimina o mapeamento implícito de uma coleção de índices n (conforme especificado por w ) para uma coleção de vetores de base canônica eₙ , que é necessário para que nossas regras de indexação atuais funcionem.

Limitando o escopo apenas à transposição vetorial por enquanto, acho que temos duas opções. Podemos cometer um erro ou podemos introduzir o tipo de covetor especial. Dada a natureza de primeira classe do vetor em Julia, acho que o primeiro seria muito difícil de vender ... e para fazê-lo de forma consistente, provavelmente deveríamos impedir completamente que os vetores participassem inteiramente da álgebra de matrizes.

Então, eu atirei no covector. É muito trabalhoso e infelizmente não poderei dedicar mais tempo a isso. Posto aqui na esperança de que alguém fuja com ele ou que aprendamos com as dificuldades e decidamos fugir. Mas eu tenho um prédio de ramificação com transposições como vistas e multiplicação de matrizes implementada com covetores. Alguns testes selecionados passam, mas apenas sem depwarn=error (por exemplo, ./julia -e 'using Base.Test; include("test/matmul.jl")' ). https://github.com/JuliaLang/julia/compare/mb/transpose

Eu defini dois novos tipos de visualização a serem usados ​​para transpose e ctranspose de matrizes e vetores:

immutable MatrixTranspose{C,T,A} <: AbstractArray{T,2}
    data::A # A <: AbstractMatrix{T}
end
immutable Covector{C,T,V} 
    data::V # V <: AbstractVector{T}
end

O parâmetro C é um booleano simples para representar se a transposição é uma transposição ou não. Observe que o Covector _não_ é um subtipo de AbstractArray ; este é um requisito muito forte para fazer o despacho funcionar razoavelmente.

Algumas desvantagens:

  • Covectors são decididamente complicados e serão um desafio documentá-los de maneira acessível e rigorosa. A linguagem pode ajudar aqui - poderíamos simplesmente chamá-los de RowVector s. Independentemente disso, a primeira dificuldade que você encontra ao tentar explicar esse comportamento é como você fala sobre a forma de um covector ( size é indefinido). Se (m,n) é a forma de uma matriz, então (m,) pode representar a forma de um vetor ... e se abusarmos da notação da tupla, podemos descrever um covetor com a forma (,n) . Isso nos permite expressar regras de álgebra de vetor / matriz misturadas com uma dimensão "ausente" que combina e se propaga de forma sensata:

    • Matriz * O vetor é (m,n) × (n,) → (m,)

    • Covector * Matrix é (,m) × (m,n) → (,n)

    • Vector * Covector é (n,) × (,n) → (n,n)

    • Covector * O vetor é (,n) × (n,) → α (escalar)

  • O número de operações binárias e tipos aqui levam a uma enorme explosão combinatória no número de métodos que precisam ser definidos ... apenas para a multiplicação, temos:

    • Mutação: (mutante, não mutante)
    • Transponha: (A, Aᵀ, Aᴴ) × (B, Bᵀ, Bᴴ).
    • Forma: (Mat × Mat; Vec × Mat; Mat × Vec, Vec × Vec). Observe que nem todos eles são suportados em todas as combinações de transposições, mas a maioria é.
    • Implementação: (BLAS, Strided, genérico, mais especializações para matrizes estruturais)

    Embora algumas dessas operações se alinhem bem com vários comportamentos de despacho e fallback, ainda é um número _grande_ de métodos para cada operador. Remover totalmente o suporte de matriz / vetor misto aqui definitivamente ajudaria a simplificar as coisas.


  • Eles não resolvem imediatamente nenhuma das dificuldades de descartar todas as dimensões escalares. O suporte a qualquer tipo de transposição vetorial quando descartamos dimensões escalares nos coloca em um território ambíguo em relação ao conjugado complexo (consulte https://github.com/JuliaLang/julia/pull/13612). Talvez pudéssemos fazer A[1,:] retornar um covector, mas não generaliza bem e seria muito estranho retornar um não AbstractArray de uma fatia. Seria ótimo se as pessoas pudessem experimentar o # 13612 e procurar especificamente os casos em que a transposição conjugada de um vetor causa problemas - seria igualmente ruim com a semântica de transposição vetorial atual e não requer a exposição de Covectors.

Algumas vantagens:

  • Usar o dispatch diretamente em vez da análise especial para Ax_mul_Bx é uma grande vitória. Ele compõe muito bem com a API do BLAS. Em geral, você deseja fazer a conjugação nos níveis mais internos dos algoritmos, então faz muito mais sentido manter essa informação com os argumentos. Para chamadas BLAS, uma função simples pode ser usada para pesquisar o caractere transposto ( ntc ).
  • A multiplicação entre vetores e covetores agora é associativa porque v'v retorna um escalar. Agora você pode avaliar v'v*v da esquerda para a direita e evitar formar a matriz.
  • Isso permite a remoção de Vector * Matrix, que só funciona se você tratar todos os vetores como se tivessem dimensões singleton posteriores ... e é um grande passo para remover completamente o suporte para dimensões singleton posteriores em geral.

Outras notas:

  • Ter a complexidade da transposição representada por um parâmetro de tipo booleano funciona bem, mas muitas vezes eu senti que tinha definido os parâmetros na ordem errada. Raramente eu realmente quis restringir ou capturar T e C . Não tenho certeza se fazer de outra maneira seria melhor em termos do número de versões de tipo de espaço reservado que você precisaria definir, mas no mínimo corresponderia a AbstractArray{T} .
  • Isso realmente agrava as dificuldades do StridedArray. Ler a tabela de métodos para * é um grande desafio com tipos como ::Union{DenseArray{T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},2},MatrixTranspose{C,T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},A<:Union{DenseArray{T,1},DenseArray{T,2},SubArray{T,1,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD},SubArray{T,2,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD}}},SubArray{T<:Union{Complex{Float32},Complex{Float64},Float32,Float64},2,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int64,Range{Int64}}}},LD}} . Claro, eu o escrevi como um typealias, mas mesmo apenas gerenciar todos esses nomes de alias de tipo diferente é uma dor ( StridedMatOrTrans , QRCompactWYQorTranspose , etc.).
  • Não tive a chance de integrá-lo ao SparseVector, mas será uma grande vitória, pois representará a transposição de forma eficiente sem a necessidade de um CSR.
  • Precisamos definir indexação escalar e / ou não escalar em Covectors? Nesse caso, qual é o resultado de r[:] ou r[1:end] ? É um vetor ou covetor? Acho que tentaria fugir sem definir isso, se pudermos. Curiosamente, o Matlab tem regras muito especiais para indexar vetores linha com outros vetores - eles tentam arduamente manter sua linha ao custo de alguns casos estranhos ( r((1:end)') é r(1:end) é r(:)' ). Tenho indexação escalar definida em meu branch por enquanto, mas talvez ela deva ser removida também. Isso deixaria claro que o Covector só deve ser usado com operações de álgebra linear que o conheçam.

Finalmente, quero apenas salientar que a maioria das vantagens que vejo aqui são igualmente aplicáveis ​​ao tipo MatrixTranspose por si só. Acho que isso demonstra que o Covector pode funcionar muito bem sem se aprofundar muito em álgebras abstratas para permitir operações mistas de vetor / matriz. Definitivamente, adiciona um bom recurso (a capacidade de usar vetores consistentemente com álgebra linear), mas não tenho certeza se vale a pena a complexidade adicional.

Por curiosidade, se você ainda se lembra de @mbauman , o que o motivou a introduzir o parâmetro booleano C ? Você não pode simplesmente ter (em pseudotraits)

transpose(::Matrix) -> MatrixTranspose
transpose(::MatrixTranspose) -> Matrix

? Não estou duvidando do seu julgamento (excepcional) aqui, apenas quero entender quais descobertas o forçaram a fazer isso.

Presumivelmente, pode-se generalizar isso para um tipo PermutedDimensionArray , com um parâmetro de tupla codificando a permutação. O tipo Covector obviamente tem um propósito diferente e precisa ser uma coisa separada.

Você precisa de alguma maneira de lidar com transposes conjugadas (e também matrizes conjugadas não transpostas para (A').' ). Vejo três maneiras óbvias de fazer isso:

  • Armazene a conjugação como um campo booleano em apenas um tipo MatrixTranspose . Após reflexão, acho que esta é definitivamente a melhor opção para fazer a interface com o BLAS externo. Em termos de JuliaBLAS nativa, porém, gostaria de ter certeza de que Julia / LLVM é capaz de içar T.isconjugate ? conj(T[i,j]) : T[i,j] dos loops.
  • Um tipo com parâmetro de conjugação. Isso é o que eu escolhi, e acho que fui influenciado pelo fato de que atualmente fazemos pseudo-dispatch no conjugate-ness por meio de Ac_mul_Bt e amigos. Ele também oferece garantias mais fortes sobre as otimizações de remoção de branch que Julia pode fazer. Mas eu não pensei muito sobre isso ... Eu só queria começar a implementação de um esboço e estava mais preocupado com o Covector.
  • Dois tipos separados, MatrixTranspose e MatrixCTranspose . Isomórfico a um parâmetro de tipo, mas acho dois tipos separados de wrappers irritantes. Supertipos abstratos e aliases de união podem ajudar, mas eu ainda escolheria o parâmetro em vez desta opção.

Suponho que haja agora uma quarta opção com funções digitadas, armazenando qualquer função de transformação dentro do tipo de transposição ... mas isso também requer um parâmetro de tipo para que a função seja rápida e, portanto, é um parâmetro de tipo que também não é facilmente despachado.

Eu me pergunto se poderíamos ter um invólucro ConjugateView , com a regra conj e transpose garantindo que ConjugateView seja colocado dentro do invólucro MatrixTranspose . Ou seja, por A a Matrix ,
A' = conj(transpose(A)) = transpose(conj(A)) todos produzem um MatrixTranspose{ConjugateView{Matrix}} (eliminando parâmetros de tipo não informativo).

Ah, sim, isso também é sensato. Tendo a pensar na transposição conjugada como uma "coisa" atômica, perdi essa opção. Eu estava pensando em como as transposições não conjugadas podem ser representadas por um tipo de índice de subarray especial, assim como as remodelações.

Estou feliz que vocês ainda estejam trabalhando nisso! Este é um tópico épico! Três vivas !!!

Isso está planejado para 1.0?

Em duas edições (# 18056, # 18136) fui apontado para este tópico gigante.
Então, eu tentei.

Agora Julia tem vetores unidimensionais verdadeiros, _ex .: mx[row,:] não é mais uma matriz 1xn.
Esta é uma mudança bem-vinda!

Mas, como efeito colateral, alguns problemas existentes tornaram-se mais óbvios.
Fui mordido pelo fato de que v*mx não funciona para vetores unidimensionais.
Matematicamente, deve funcionar naturalmente e retornar um vetor 1-d,
quando o produto geral a*b é definido por contrato
o último índice do primeiro termo e o primeiro índice do segundo termo.

No momento, a assinatura do método do produto Vector-Matrix é:
(*)(A::AbstractVector, B::AbstractMatrix) = reshape(A,length(A),1)*B
e este método é usado para o caso v*v' e não para v*mx .
(Obrigado @andreasnoack por apontar isso.)
Obviamente, um único método não pode ser usado para ambos.

Parece que Julia está lutando com algumas convenções do tipo Matlab.
Mas no Matlab não há vetores 1-d, apenas matrizes 1xn e nx1,
tantas coisas que são naturais podem haver um problema aqui.
Julia tem vetores 1-d verdadeiros, o que deve ser uma grande vantagem.
Seria bom alcançar um estado próprio realmente consistente.

Fortran é um exemplo muito melhor e mais consistente a seguir neste caso.
A operação transpose é definida apenas para matrizes em Fortran,
criar uma matriz 1xn a partir de um vetor 1-d verdadeiro simplesmente não é uma transposição.
Para matmul veja o excerto do livro Metcalf citado em # 18056.

Eu acho que a maioria dos pontos originais de @alanedelman estavam corretos.

Então, aqui está uma sugestão que simplesmente curaria alguns problemas existentes,
respeitando o estado atual, tanto quanto possível:

  • mantenha v' como está para criar uma matriz 1xn a partir de um vetor 1-d verdadeiro v
  • rowmx funções colmx seriam melhores, mas v' é muito difundido para mudá-lo
  • já temos a função vec para criar um vetor 1-d verdadeiro
  • embora o inverso de v' não seja v'' mas vec(v') , podemos conviver com isso
  • o produto a*b deve sempre contrair o último índice de a e o primeiro índice de b
  • o operador * não deve ser usado para produtos internos ou externos
  • para produtos internos já temos a função dot
  • para produtos externos, o uso de * deve ser eliminado (_ isto é, a sintaxe de v*v' )
  • para produtos externos, uma nova função deve ser usada
  • um operador infixo correspondente poderia manter a sintaxe leve

Perder [sintaxe matemática concisa para] produtos externos seria lamentável. Prefiro desistir do vec * mat pessoalmente.

O PernutedDimsArray existente já pode lidar com o caso em que pai e visualização têm números diferentes de dimensões? Nesse caso, talvez isso já seja utilizável como o tipo de wrapper de transposição não conjugado, mesmo para pais de vetor.

Não encontrei PermutedDimsArray na documentação.

Mas sinto que, em vez de inventar ainda mais tipos de dados,
apenas os três tipos de produtos de matriz devem ser separados claramente.
Os produtos internos já estão separados.
Precisamos apenas separar os produtos normais dos externos.
Produtos externos não seriam perdidos, apenas sua sintaxe mudaria.
Considere o caso v*mx apenas como um sintoma de um problema mais profundo.

Além do "problema de métodos ausentes", não seria um tipo com o qual você teria que se preocupar, seria um detalhe de implementação que .' retorna um tipo de wrapper preguiçoso (e ' uma versão conjugada do mesmo). Caso contrário, não acho que poderíamos ter vec*mat e vec*vec' usando o mesmo operador * . Se eu visse vec*mat em um papel, pareceria errado para mim, mas vejo vec*vec' bastante frequência.

Pensando mais sobre isso, porém, acredito que PermutedDimsArray não transpõe recursivamente seus elementos para arrays de arrays, portanto, não é muito adequado como o tipo de invólucro para usar aqui.

A outra alternativa é proibir totalmente a transposição vetorial. Acho que a discussão aqui já foi excessivamente completa e estamos apenas aguardando uma implementação abrangente de uma ou ambas as opções para avaliar como será o impacto.

Agradeço que você estivesse pronto para uma discussão enquanto este tópico estava próximo do fim.

Embora v*mx possa parecer estranho para você, ele é muito usado em código cristalográfico.
Também é bem tratado por matmul Fortran. (Veja # 18056)

De volta ao produto u*v' .
Quando u e v são matrizes nx1, este é um produto de matriz normal,
que acaba por fornecer o mesmo resultado que o produto externo.
Mas isso é apenas usar o produto de matriz para emular o produto externo.
Isso é perfeito no mundo do Matlab, onde tudo é uma matriz.

Em Julia, temos verdadeiros vetores 1-d que estão mais próximos do mundo de Fortran.
Em Julia o vetor transposto v' já é um problema,
A escolha de Fortran é proibir, como já foi sugerido para Julia por outros.

Fortran não tem função intrínseca para produtos externos.
Julia prontamente transforma v' em uma matriz 1xn,
e executa a operação * em um vetor 1-d e uma matriz 1xn.
Devido ao envio múltiplo, é capaz de fazer isso,
mas aqui * certamente não é mais um produto de matriz.

O ponto em que Fortran e Julia se comportam de forma semelhante
é que ambos usam uma função dot separada para produtos internos.
Houve uma discussão para agrupar dot também no operador * ,
mas felizmente isso não aconteceu.

Portanto, temos que lidar com os três produtos da álgebra linear:
produto de matriz normal, produto interno e produto externo.
Atualmente, o operador * é uma sintaxe combinada para produtos normais e externos.
Tudo o que sugeri é separá-los e alcançar um estado mais consistente.

(Ah, deixei de fora o produto vetorial, mas já está bem separado)

Não acho que produto interno, produtos externos e multiplicação de matrizes sejam uma maneira matematicamente correta de dividir / classificar produtos. O que se segue é certamente uma repetição de coisas que foram afirmadas acima por várias pessoas, mas dada a extensão deste tópico, espero que seja possível incluir um resumo de vez em quando. Este é o meu resumo pessoal e certamente não sou um especialista, então corrija-me onde estou errado.

Em uma configuração de álgebra linear abstrata, os jogadores principais são os vetores v (vivendo em um espaço vetorial V ), mapas lineares (atuando em um espaço vetorial V e mapeando para um espaço possivelmente diferente W ) e composição desses mapas, formas lineares ou covetores (vivendo em um espaço duplo V* e mapeando de V para escalares), produtos internos (de V × V para escalares), produtos tensores entre vetores (ou seja, kron ). Produto externo Prefiro pensar em um produto tensorial entre um vetor e um covetor.

De particular interesse para esta questão é o isomorfismo entre vetores e formas lineares se um produto interno for definido, ou seja, para cada f vetores de mapeamento v para escalares, existe um w de modo que f(v) = dot(w,v) para qualquer v . Entretanto, os covetores podem existir sem referência a produtos internos (por exemplo, o gradiente de uma função multidimensional).

Para representar todos esses objetos em um computador, você normalmente escolhe uma base e pode então representar a maioria dessas coisas usando álgebra matricial. Ou seja, você só precisa da multiplicação e transposição da matriz se estiver disposto a ser flexível com as dimensões do singleton à direita (vetores como matrizes nx1, escalares como matrizes 1x1 etc.). Esta foi a opinião do Matlab, bem como a maneira como muitos livros e artigos são escritos, especialmente em álgebra linear numérica.

Nesse caso, o referido isomorfismo de vetores para covetores (assumindo implicitamente o produto interno euclidiano) corresponde a tomar a transposta (conjugado de Hermit no caso complexo) da representação matricial de um vetor. Porém, no sentido abstrato, não há noção da transposição de um vetor, provavelmente por isso não é definido em Fortran, como afirma @GaborOszlanyi . Apenas a transposta de um mapa linear é definida (isso não requer um produto interno e sua representação de matriz corresponde à matriz transposta mesmo no caso complexo), bem como o adjunto de um mapa linear (isso requer um produto interno) .

Por causa da importância da estabilidade de tipo no código Julia, a abordagem de "apenas matrizes" do Matlab (com dimensões singleton à direita flexível) não funciona bem. Mas também não queremos fazer a transição para a configuração totalmente abstrata e continuar trabalhando na configuração típica (produtos internos euclidianos, mapeamento trivial de vetores para covetores, ...). Já que no final estamos escrevendo código e queremos usar caracteres ASCII, precisamos extrair o máximo possível dos símbolos * , ' e .' . Felizmente, é aqui que o envio múltiplo se torna útil e leva às várias propostas mencionadas acima. Fiz uma tabela sobre isso, ou seja, como as operações abstratas de álgebra linear se traduziriam em métodos julia específicos (não funções).

Como uma nota final para @GaborOszlanyi , ainda não encontro lugar para v*A em tudo isso. Isso pode ser padrão em campos onde os vetores são, por padrão, denotados como matrizes de linha, mas pessoalmente acho que é uma escolha estranha. Se os mapas lineares f e g agirem como f(v) = v*A e g(v) = v*B , então isso significa que g(f(v)) = (g ◦ f)(v) = v*A*B que é estranho desde a ordem de composição é trocado. Se houver, eu poderia interpretar isso como um produto interno incompleto, como na penúltima linha da tabela vinculada.

Seu resumo é profundo e convincente.
Obrigado por explicar isso tão bem.

Eu tenho apenas duas perguntas restantes:

  • Que mudanças realmente acontecerão em Julia como resultado dessa discussão aprofundada?
  • Por que o Fortran implementou v*mx em matmul ?

Existem dois problemas expostos por este problema:

A. Os matemáticos aplicados estão ligados à notação de Householder, que implicitamente faz uso de dois isomorfismos naturais:

  1. Um vetor de comprimento N é indistinguível de uma matriz de _coluna Nx1, uma vez que as matrizes Nx1 formam um espaço vetorial. ("colunificar", ▯)
  2. Uma matriz 1x1 é indistinguível de um número escalar, uma vez que matrizes 1x1 podem ser definidas com todas as propriedades algébricas dos escalares. ("escalar", ■)

O problema é que nenhum desses isomorfismos é natural para expressar nos tipos de Julia e ambos envolvem verificações de tempo de execução da forma do array. As regras de dimensão de singleton à direita do MATLAB podem ser pensadas como uma implementação de ambos os isomorfismos.

B. Um array bidimensional pode ser definido recursivamente como um array de arrays. No entanto, uma matriz é uma linha de colunas ou coluna de linhas, mas nunca uma linha de linhas ou coluna de colunas. O fato de que as matrizes não podem ser definidas recursivamente destaca a estrutura tensorial diferente de matrizes e arranjos n-dimensionais. Falando de maneira apropriada, os arrays multidimensionais são um tipo muito limitado de tensor e não possuem o maquinário completo necessário para implementar o último. Como uma contração estritamente falando é uma operação sobre o emparelhamento de um espaço vetorial com seu dual, é um equívoco falar em contrair os índices de matrizes multidimensionais, que nunca invocam o conceito de espaço vetorial dual. A maioria das pessoas _não_ quer o maquinário completo, o que requer a preocupação com índices co- / contravariantes ou para cima / baixo com natureza linha / coluna. Em vez disso, a maioria das pessoas quer que os arrays sejam contêineres simples e antigos, totalmente contravariantes em todas as dimensões, _exceto em duas dimensões_, pelo que a maioria dos usuários quer pensar em arrays bidimensionais como tendo a álgebra de matrizes (que são tensores down-up), e nunca os tensores down-down, up-up ou up-down. Em outras palavras, os arrays bidimensionais desejam ter uma caixa especial.


Eu ainda poderia ser persuadido do contrário, mas aqui está minha proposta atual de três partes:

a) Proibir a transposição de vetores inteiramente, exigindo que os usuários convertam explicitamente vetores em matrizes de coluna para escrever expressões de estilo Householder como u'v , u*v' , u'*A*v e u'*A*v/u'v . Todas essas expressões podem ser construídas a partir de apenas três operadores unários e binários elementares: matmul, transposição de matriz e divisão de matriz. Em contraste, se u e v são vetores verdadeiros, então não é possível dar subexpressões como o significado de u' ou u'*A sem introduzir um TransposedVector especial TransposedVector , que como consequência lógica requer matrizes multidimensionais para se preocupar com os índices para cima / para baixo em sua estrutura tensorial, o que parece um preço muito alto a pagar.

Uma limitação de (a) seria que todas as expressões de estilo Householder gerariam matrizes 1x1 em vez de escalares verdadeiros (o que ainda é uma grande melhoria em relação a u'v retornando um vetor 1), então uma expressão como (u'*v)*w ainda não funcionaria. Na prática, não acho que expressões de "produto triplo" como essa ocorram com frequência.

b) Introduzir uma notação alternativa para as operações análogas em vetores, como

  • u ⋅ v = scalarize(columnify(u)'*columnify(v)) para o produto interno (ponto)
  • u ⊗ v = columnify(u)*columnify(v)' para o produto externo (Kronecker)
  • A(u, v) = scalarize(columnify(u)'*A*columnify(v)) , uma notação antiga para a forma bilinear
  • A(u) = A(u, u) para a forma quadrática

As expressões em (b) diferem de suas contrapartes em (a) por escalarizar automaticamente as expressões para produto interno e formas bilineares / quadráticas, evitando assim a formação de matrizes 1x1.

c) Tornar as matrizes 1x1 conversíveis em escalares verdadeiros, de modo que o código como

M = Array(Int, 1, 1, 1)
a::Int = M

poderia trabalhar. Tudo o que seria necessário é fazer a verificação do tempo de execução para o tamanho do array com algo como:

function convert{T}(::Type{T}, A::Array{T,N})
    if length(A) == 1
        return A[1]
    else
        error()
    end
end

Esta proposta é uma pequena modificação do que foi proposto por Folkmar Bornemann cerca de dois anos atrás. Quando tentamos, o custo da verificação de tempo de execução não era muito grande e só seria invocado em atribuição de tipo forçado (que é, na verdade, uma chamada convert disfarçada), não em atribuição geral.

@jiahao , a legibilidade desta postagem é limitada pelos caracteres difíceis de renderizar. Nem parece certo no OS X, que geralmente tem quase Unicode em suas fontes.

@jiahao , eu certamente poderia concordar com a maioria / tudo isso, embora eu gostaria de desafiar dois pontos:

sem introduzir um tipo especial TransposedVector , o que, como consequência lógica, requer que os arrays multidimensionais se preocupem com os índices up / down em sua estrutura tensorial, o que parece um preço muito alto a pagar.

Isso só me parece uma consequência lógica se você quiser fazer de TransposedVector um subtipo da hierarquia AbstractArray , que presumi que não era o plano (ao contrário de algum tipo de LazyTranspose que realmente é apenas outro tipo de AbstractMatrix ).

u ⊗ v para o produto externo (Kronecker)

Se o objetivo é não confiar em isomorfismos implícitos e separar claramente as operações de matriz de operações de vetor e produtos mais abstratos, acredito que isso falhe pelo seguinte motivo (não tenho certeza sobre o acordo universal sobre as seguintes definições matemáticas):

  • O produto Kronecker A ⊗ B é uma operação definida em matrizes, não em vetores.
  • Portanto, ao invés de ler u ⊗ v como o produto tensorial de dois vetores, isso daria origem a um tensor bidimensional do tipo down down. A única maneira de obter uma 'matriz' adequada seria pegar o produto tensorial de um vetor com um covetor. Mas, como você deseja evitar a introdução desses objetos, parece impossível obter o resultado desta operação antes de colunificar os dois vetores envolvidos.
  • Um terceiro nome para u ⊗ v é o produto externo, que normalmente é definido de maneira desleixada, e não tenho certeza se existe uma definição rigorosa aceita em termos de para cima e para baixo. Algumas fontes afirmam que é equivalente ao produto tensorial de dois vetores, daí o ponto anterior. Se, em vez disso, você aceitar uma definição em que 'produto externo' também significa mapear implicitamente o segundo vetor para um covetor de modo a obter um tensor ascendente, então não há problema.

Em vez disso, a maioria das pessoas deseja que os arrays sejam contêineres simples e antigos, totalmente contravariantes em todas as dimensões, exceto em duas dimensões,

Já consideramos a opção nuclear? Começamos a álgebra linear novamente, removendo completamente os typealias de AbstractVector e AbstractMatrix e substituindo-os por:

abstract AbstractVector{T} <: AbstractArray{T,1}
abstract AbstractMatrix{T} <: AbstractArray{T,2}

# and we could introduce:
abstract AbstractCoVector{T} <: AbstractArray{T,1}

Eu entendo que há muita precipitação, mas podemos acabar com uma separação clara entre matrizes de armazenamento multidimensionais e álgebra linear. Não _temos_ que implementar a álgebra tensorial multidimensional completa, espaços vetoriais duais, etc. Nós apenas temos que implementar os bits que muitas pessoas querem: vetores, covetores e matrizes. Obtemos produtos internos, produtos externos, produtos de matriz de covetor, produtos de matriz de vetor e produtos de matriz de matriz. Transpor é definido em todos os itens acima. Podemos ter implementações de AbstractMatrix que realmente usam um array 1D de arrays 1D (não a implementação padrão, é claro). Não precisamos usar a notação Householder (que IMHO é uma das fraquezas do MATLAB!), Mas ainda podemos obter todas as conveniências da álgebra linear.

Estou um pouco nervoso para sugerir isso, mas acredito que permitirá a Julia imitar o modelo "correto" do que a maioria das pessoas aprende em, por exemplo, álgebra linear de nível universitário do primeiro ano, sem a necessidade de recorrer às convenções de Householder. Fazer uma distinção clara também pode tornar mais fácil mover Base.LinAlg para um pacote de "biblioteca padrão", o que acredito ser um objetivo de longo prazo para Julia. Também combina bem com a ideia de que haverá uma nova coisa 1D redimensionável semelhante a uma lista que virá com as novas Buffer alterações e implementação nativa de Array , portanto, este tipo de "lista" genérica pode substituir Vector por muitas das partes centrais de Julia e vamos carregar o pacote LinAlg bem tarde, tendo Array e "lista" definidos bem cedo.

Existem muitos algoritmos que já foram "simplificados" e que são expressos em termos de matrizes Fortran e não em termos de álgebra linear adequada. Julia precisa ser capaz de implementar esses algoritmos sem que as pessoas tenham que redescobrir (ou coagir) uma estrutura de álgebra linear em cima de matrizes multidimensionais.

Em minha linha de trabalho, uma álgebra linear apropriada que mapeia tensores e índices co / contravariantes etc. em matrizes Fortran é provavelmente a melhor. Para fazer isso funcionar - e para não confundir as pessoas - eu deixaria de lado os termos "vetor" e "matriz" e "matriz", mantendo-os em um nível baixo, e usaria outros termos (mais elaborados?) Para tudo de nível superior . Esse nível superior também deve abstrair as opções de armazenamento, seja por meio de tipos abstratos ou via parametrização. Talvez um prefixo LA seja o caminho a percorrer para expressar isso:

LA.Vector
LA.CoVector
LA.Tensor{... describing co/contravariance of indices ...}

Vetores e matrizes são então puramente usados ​​para armazenamento. No nível baixo, a transposição é gerenciada manualmente (o mesmo que no BLAS); no nível superior, ele é tratado automaticamente.

Isso está próximo da sugestão de

@eschnett Acho que no início deste tópico foi decidido que a álgebra multilinear seria deixada para os pacotes, em vez da Julia base. Eu acho que muitas dessas idéias como você sugere poderiam ser concretizadas em um novo pacote que trata tensores, espaços vetoriais, co / contra-variância e assim por diante dentro do sistema de tipos, um pouco diferente do pacote _TensorOperations.jl_ que fornece funções de conveniência para multiplicar matrizes como se fossem tensores. Acho que seria um desafio desenvolver, mas potencialmente uma abstração que vale a pena!

Quanto a deixar Matrix e Vector sozinhos - bem, talvez as definições atuais sejam perfeitas, ou talvez haja algo melhor que possamos fazer para as pessoas que desejam multiplicar matrizes e transpor vetores, usando matemática padrão notação. Eu acho que pode adicionar uma pequena curva de aprendizado para novos usuários, embora eu esperasse que se o sistema fosse óbvio e eloqüente e uma _melhora_ em outras línguas, então deveria ser fácil de aprender.

Observe que um espaço vetorial complexo não euclidiano geral V tem 4 espaços associados: V , conj(V) , dual(V) e conj(dual(V)) . Portanto, um tensor geral possui 4 tipos de índices (normalmente denotados como para cima ou para baixo, barrados ou não barrados). Em um espaço euclidiano complexo (por exemplo, mecânica quântica), dual(V) ≡ conj(V) e conj(dual(V)) = V . Em um espaço real (não euclidiano) (por exemplo, relatividade geral), V ≡ conj(V) . Em ambos os últimos casos, apenas os índices para cima e para baixo são necessários.

Em um espaço euclidiano real (a maioria das aplicações?), Todos eles são equivalentes, e as matrizes simples de julia são suficientes para representar tensores. Portanto, pelo menos para números reais, as duas operações a seguir permitem construir uma álgebra tensorial completa e consistente.

  • contrato / produto interno , que poderia ser generalizado para matrizes arbitrárias de classificação N usando a regra: contrair o último índice da primeira matriz com o primeiro índice do segundo (ou dot numpy A ∙ B retorna uma matriz de classificação M+N-2 se A e B tivessem classificação M e classificação N .
  • produto tensorial : A ⊗ B retorna uma matriz de classificação N+M

Especializado em vetores v , w e matrizes A , B , isso permite escrever todos os seguintes:

  • produto interno do vetor v ∙ w -> excepcionalmente retorna um escalar em vez de uma matriz de classificação 0
  • multiplicação de vetores de matriz A ∙ v
  • multiplicação de matriz de matriz A ∙ B
  • tensor / produto externo v ⊗ w
  • covector (=== vector) multiplicação de matriz v ∙ A

Embora se possa querer usar * para , a armadilha é que a definição acima de não é associativa: (A ∙ v) ∙ w ≠ A ∙ (v ∙ w) porque o último não ser definida.

Mas, novamente, isso é apenas se você estiver disposto a ignorar matrizes complexas.

Quanto a uma implementação completamente geral de tensores, comecei (e abandonei) isso há muito tempo, antes que houvesse tuplas alocadas na pilha, funções geradas e todas essas outras coisas que provavelmente a tornariam mais viável hoje.

Abordagem interessante, @Jutho. Parece bastante intuitivo. Estou _segurando_ que essas definições podem ser adicionadas independentemente do que fizermos com * e ' , e eu apoiaria isso.

Mas, novamente, isso é apenas se você estiver disposto a ignorar matrizes complexas.

conj() inseridos manualmente corrigem isso. E não acho que Julia queira ignorar matrizes complexas, não é?

Notei mais algumas coisas sobre ter AbstractMatrix como um subtipo especial em vez de um alias de tipo:

matrix[:,i] -> AbstractVector{T}
matrix[i,:] -> AbstractCoVector{T}
array_2d[:,i] -> AbstractArray{T,1}
array_2d[i,:] -> AbstractArray{T,1}

Isso é muito legal - podemos obter vetores de coluna e linha indexando uma matriz. Se for apenas um contêiner de armazenamento 2D, obtemos um array de armazenamento 1D. Deve abranger todos os casos de uso! E ainda obedece à interface AbstractArray com regras de fatiamento APL, uma vez que AbstractVector{T} <: AbstractArray{T,1} e AbstractCoVector{T} <: AbstractArray{T,1}

Um fato "interessante" é

array_3d[:,:,i] -> AbstractArray{T,2}
matrix(array_3d[:,:,i]) -> `Matriz Abstrata {T}

então você teria que envolvê-lo manualmente como uma matriz se quiser fazer a multiplicação de matrizes com o resultado. Isso é um sinal de mais ou de menos, não sei? Mas isso parece ser uma complicação potencial e toca no que @eschnett mencionou sobre como tornar mais fácil para usuários de outras linguagens que combinam armazenamento e álgebra linear.

Esta pode ser uma pergunta boba, mas @jutho mencionou anteriormente que escrever código era limitado por ASCII. Por que ainda estamos nos limitando a um conjunto de caracteres de 7 bits desenvolvido em 1963 e atualizado pela última vez em 1986 (30 anos atrás)? Esta foi uma época em que os famosos 640 KB era a RAM máxima disponível em um PC em 1981. No mercado de computadores de hoje, agora temos comumente processadores de 64 bits com 32 GB de RAM à venda (50.000 vezes o máximo anterior) e não estamos nem perto de o limite teórico para processadores de 64 bits. Então, por que ainda nos limitamos a um conjunto de caracteres desenvolvido há 40 anos?

Podemos usar Unicode, apenas tenha em mente que infelizmente o tamanho do teclado não cresceu por um fator semelhante nos últimos 40 anos, nem o número de dedos que uma pessoa normal tem (nos últimos 10000+ anos afaict).

IMHO, ASCII deve ser colocado para descansar. Um teclado matemático especial para programação rápida de símbolos matemáticos é realmente uma boa ideia! Existe um bom motivo para não usar mais símbolos que vêm com UTF? Você pode justificar o esforço de tentar extrair tudo o que for possível do ASCII?

@ esd100 : Não use este problema do GitHub para discussões altamente especulativas.

@Johnmyleswhite. Não tenho certeza do que você quer dizer com "especulativo". Tem certeza de que essa é a palavra que pretendia usar? Achei que o uso de ASCII vs qualquer outra coisa fosse relevante para a discussão, uma vez que parece que a linguagem, os operadores e a funcionalidade estão ligados aos símbolos usados. Não sou um especialista de forma alguma, mas parece um problema para discutir, ou pelo menos ser claro, relacionado à funcionalidade que está sendo abordada.

Quero dizer, há algum motivo pelo qual não podemos definir a linguagem da maneira que queremos (com os objetivos fundamentais em mente: facilidade de uso, velocidade)? O uso de símbolos / palavras especiais não tornaria a linguagem mais rica e bonita?

@ esd100 observe que, por exemplo, o comentário mais recente de @Jutho (usando 2 símbolos Unicode) foi bem recebido e @yuyichao concorda que podemos usar Unicode. Existem outras propostas sobre a introdução de mais operadores somente Unicode (por exemplo, o símbolo de composição para funções de composição, # 17184). Eu não acho que as pessoas estão discordando de você (embora tenhamos considerações práticas a ter em mente), mas se você tiver sugestões _específicas_ sobre o assunto, entre em contato conosco.

Estou um pouco confuso com este novo comportamento em v0.5 que acredito ter surgido desta discussão:

julia> [ x for x in 1:4 ]' # this is fine
1×4 Array{Int64,2}:
 1  2  3  4

julia> [ Symbol(x) for x in 1:4 ]' # bit this isn't? What is special about symbols?
WARNING: the no-op `transpose` fallback is deprecated, and no more specific
`transpose` method for Symbol exists. Consider `permutedims(x, [2, 1])` or writing
a specific `transpose(x::Symbol)` method if appropriate.

Como faço um vetor linha (não numérico) a partir de uma compreensão de lista? Tem que ser um vetor de linha. (Isso seria melhor como um problema separado ou discutido em outro lugar? Não sabia onde postar ...)

Você pode usar remodelar: reshape(v, 1, length(v)) . Talvez isso também deva ser mencionado no aviso de suspensão de uso. Acho que a ideia é que transpor é uma operação matemática e, portanto, só deve ser definida para vetores / matrizes matemáticas.

É mencionado no depwarn: permutedims(x, [2, 1]) .

permutedims não funciona para vetores. Veja esta nova edição: # 18320

Acho que a ideia é que transpor é uma operação matemática e, portanto, só deve ser definida para vetores / matrizes matemáticas.

Isso não faz sentido para mim. Isso está em discussão? Eu realmente preferiria transpor para trabalhar em matrizes não numéricas, a menos que haja uma justificativa realmente boa.

Acho que a ideia é que transpor é uma operação matemática e, portanto, só deve ser definida para vetores / matrizes matemáticas.

Isso não faz sentido para mim. Isso está em discussão? Eu realmente preferiria transpor para trabalhar em matrizes não numéricas, a menos que haja uma justificativa realmente boa.

Acho que é relativamente complexo satisfazer o que você deseja _e_ faz sentido para a matemática. Em um sentido matemático, transpor é frequentemente definido trocando espaços vetoriais e seus espaços duais de um vetor ou matriz (bem, há alguma complicação com transpor vs conjugado transposto, também). Para uma matriz M de números regulares, temos a transposta como permutedims(M, (2,1)) , mas em geral você pode definir, por exemplo, uma matriz de "bloco" em termos de submatrizes como esta:

M = [A B;
     C D]

onde A etc são as próprias matrizes. Meu entendimento é que Julia gosta de ter

M' = [A' C';
      B' D']

que é o que você faria usando matemática de caneta e papel e, portanto, "sintaxe desejável".

Isso significa que os elementos de uma matriz devem aceitar ' e .' . Para números, estes são definidos como conjugação complexa e sem operação, respectivamente. IMHO Eu acho que isso é um trocadilho de conveniência, mas funciona e, o mais importante, é implementado com regras _simple_ ("transpor é recursiva" - em vez de precisar de BlockMatrix classes e assim por diante). Mas esse trocadilho com a transposição foi removido dos tipos não numéricos em 0,5, porque não faz muito sentido. Qual é a transposição de Symbol ?

Se você tiver _dados_, não números, então usar permutedims está perfeitamente bem definido em seu significado e comportamento: não é recursivo. Usar um trocadilho matemático como .' pode economizar alguns caracteres de digitação - mas fará _muito_ mais sentido para outra pessoa (que pode não ser extremamente familiarizada com matemática ou MATLAB) ler seu código se você estiver usando reshape e permutedims conforme necessário. _Para mim, este é o_ ponto mais _importante_.

IMHO, esta questão muito longa trata de fazer uma interface matematicamente consistente para álgebra linear, e parece natural para mim que o resultado seja menos trocadilhos matemáticos para conjuntos de dados.

Obrigado pela resposta atenciosa. Eu ainda discordo fortemente

Mas esse trocadilho com a transposição foi removido dos tipos não numéricos em 0,5, porque não faz muito sentido. Qual é a transposição de um símbolo?

Não tenho certeza se definir a transposição de Symbol como não operacional faz menos sentido do que definir a transposição em um número real. Eu entendo que o "trocadilho" é conveniente, mas parece que a coisa "correta" a fazer seria ter a transposição definida apenas para vetores e matrizes e transpose(A::Matrix{Complex}) aplicar conj como parte de sua execução.

Usar um trocadilho matemático como .' pode economizar alguns caracteres de digitação - mas fará muito mais sentido para outra pessoa (que pode não ser extremamente familiarizada com matemática ou MATLAB) ler seu código se você estiver usando remodelar e permutar conforme necessário. Para mim, esse é o ponto mais importante.

Eu concordo que .' deve ser usado com cautela e que uma chamada mais explícita para transpose geralmente é melhor. Acho que reshape e permutedims podem exigir uma quantidade não trivial de sobrecarga cognitiva para ler e codificar em primeiro lugar. Seja honesto, o que é mais rápido de analisar:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

Mesmo em casos simples como este, você precisa pular do início da linha (para ler reshape ) até o fim (para ler length(A) ) para entender o que está acontecendo. (Você provavelmente voltará ao meio para entender por que length(A) está lá em primeiro lugar.)

Para mim, o maior problema é que se eu sou uma nova Julia (o que significa que provavelmente já usei numpy e MATLAB antes) e vejo que isso funciona:

[ x for x = 1:10 ]'

Naturalmente, vou assumir que a mesma coisa funcionará para matrizes de strings, símbolos, etc. Não vou ler longas discussões online para descobrir a semântica de .' - estou vou tentar algumas coisas e generalizar / inferir por experiências anteriores. Talvez ter uma mensagem de erro melhor ajude aqui.

No geral, não vejo como manter a transposição sem operação em Symbol e outras entradas não numéricas interfere com a bela estrutura matemática proposta aqui (um objetivo válido!). Mas manter o ambiente autônomo parece inofensivo.

Mas manter o ambiente autônomo parece inofensivo.

Você realmente não pode definir nada de forma significativa para Any porque tudo é um subtipo de Any . A definição transpose(x)=x é totalmente errada para qualquer tipo de matriz e, para evitar alguns dos erros silenciosos, tivemos que adicionar essas definições . Portanto, é uma troca entre a conveniência de permitir a sintaxe estranha relativa ' para uma operação completamente não matemática e evitar erros silenciosos.

Não tenho certeza se definir a transposição de um símbolo como um no-op faz menos sentido do que definir a transposição em um número real. Eu entendo que o "trocadilho" é conveniente, mas parece que a coisa "correta" a fazer seria ter a transposição definida apenas para vetores e matrizes e transpose(A::Matrix{Complex}) aplicar conj como parte de sua execução.

Não discordo totalmente, mas precisaríamos implementar algum tipo de BlockMatrix ou ter métodos especiais definidos para Matrix{M<:Matrix} . Não tenho certeza se é uma ideia popular ou não? (Esta é uma questão séria para aqueles que o têm acompanhado, pois pode simplificar algumas dessas questões relacionadas).

Seja honesto, o que é mais rápido de analisar:

transpose([ f(x) for x = 1:length(A) ])
reshape([ f(x) for x = 1:length(A) ], 1, length(A))

A segunda, porque não me identifico com / como a atual transposição de vetores de Julia (claramente, _Eu considero as transposições de vetor_ muito _seriamente_ :)) Se eu tivesse que fazer a segunda, escreveria rowvec = reshape(colvec, (1, n)) , ou provavelmente [f(x) for _ = 1:1, x = 1:n] para forçar a compreensão para fazer a forma correta para começar, ou se você realmente gosta de .' então map(f, (1:n).') e f.((1:n).') também funcionam atualmente.

é uma troca entre a conveniência de permitir a sintaxe estranha relativa 'para uma operação completamente não matemática e evitar erros silenciosos

Se isso estava causando erros silenciosos e outros problemas, acho que provavelmente vou perder esse argumento. (Não vejo por que isso causaria erros - mas acredito em você.) Por outro lado ...

precisaríamos implementar algum tipo de BlockMatrix ou ter métodos especiais definidos para Matrix {M <: Matrix}

Posso estar errado, mas acho que você só precisa fazer o segundo, o que parece uma opção razoável para mim. Mas isso está começando a ficar acima do meu salário.

[f (x) para _ = 1: 1, x = 1: n]

Eu esqueci disso! Provavelmente é isso que vou acabar fazendo. No geral, ainda discordo dos seus gostos por código legível, mas de cada um no seu! ¯\_(ツ)_/¯

claramente, levo a transposição vetorial muito a sério

Sim. Acho que sim. 😉

Isso (https://github.com/JuliaLang/julia/issues/16790) também tornaria o uso de reshape no lugar de transpose um pouco mais palatável para mim.

Posso estar errado, mas acho que você só precisa fazer o segundo (editar: ter métodos de transposição especiais definidos para Matrix{M<:Matrix} ), o que parece uma opção razoável para mim.

Infelizmente agora voltamos à distinção entre dados e álgebra linear mais uma vez. Você quer que matrizes de blocos de álgebra linear tenham transposição recursiva, mas matrizes de dados 2D genéricas de matrizes de dados 2D não tenham transposição recursiva ... mas enquanto Matrix{T} e Array{T,2} são a mesma coisa, não podemos faça isso.

Isso (# 16790) também tornaria o uso de reshape em vez de transpose um pouco mais palatável para mim.

Verdadeiro!!

Você deseja que matrizes de blocos de álgebra linear tenham transposição recursiva, mas matrizes de dados 2D genéricas de matrizes de dados 2D não tenham transposição recursiva

Isso não parece algo que eu obviamente gostaria ... Por que não apenas ter duas funções diferentes para lidar com esses diferentes tipos de transposições? Acho que perdi o memorando sobre haver uma distinção muito estrita entre objetos de álgebra linear e objetos de dados.

Ler todo este tópico parece uma tarefa difícil, mas talvez eu deva fazer isso antes de comentar mais. Não quero adicionar ruído desinformado.

Acho que perdi o memorando sobre haver uma distinção muito estrita entre objetos de álgebra linear e objetos de dados.

Não há uma distinção estrita entre objetos de Álgebra Linear e objeto de Dados.
Não na implementação atual, nem no uso ideal.

Se houvesse, a modificação do tamanho (com push! ) ou mesmo os valores dos objetos de Álgebra Linear não seriam suportados (e esses usuários estariam usando StaticArrays.jl etc), e broadcast apenas ser suportado em objetos de Álgebra Linear.
Objetos de dados seriam modificáveis, expansíveis e suportariam map , (e reduce , e filter ), mas não broadcast .

Mas não vivemos em um mundo onde as pessoas pensem tanto em objetos binários de dados quanto em objetos de álgebra linear.
Portanto, este tópico de 2,5 anos, 340 comentários.


Re a transposição no-op .
Poderíamos adicionar, no alto da hierarquia de tipos, um tipo abstrato Scalar e Nonscalar .
Scalars todos fallback para uma transposição não operacional.
Nonscalars não tem fallback, (mas por enquanto recorra a um aviso de suspensão de uso)

Números, caracteres, Strings e talvez até Tuplas seriam Scalar e teriam transposição autônoma definida. Alguns escalares, por exemplo, Números complexos, sobrescreveriam esta definição de transposição.

Arrays (matrizes, vetores e outros) seriam subtipos de Nonscalar e teriam transposição recursiva.

Não tenho certeza se gosto disso ou não.

A recente onda de postagens refaz as discussões sobre _matrix transpose_ e tipos "escalares" em # 18320 # 13171 # 13157 # 8974.

Eu realmente gostaria de dissipar a noção de que o fallback não operacional transpose(x)=x é inofensivo. Em minha opinião, um fallback ainda deve produzir o resultado correto, apenas mais lentamente do que um algoritmo otimizado. É perigoso quando o método de fallback calcula silenciosamente a resposta errada, porque significa que o fallback não tem a semântica correta: transpose(x) significa coisas diferentes dependendo do tipo de x .

O fallback de transposição no-op não é apenas errado para matrizes de matrizes, mas é errado para todos os objetos do tipo matriz que não são subtipos de AbstractMatrix (que por # 987 significa que eles têm entradas armazenadas explicitamente, _não_ que eles têm a álgebra de matrizes). Aqui está um exemplo de criança-pôster (que temos alguns):

julia> A = rand(5,5); F = qrfact(A); R = F[:R]; Q = F[:Q] #Q is an example of a matrix-like object
5x5 Base.LinAlg.QRCompactWYQ{Float64,Array{Float64,2}}:
 -0.518817    0.0315127   0.749223    0.410014  -0.0197446
 -0.613422   -0.16763    -0.609716    0.33472   -0.3344   
 -0.0675866   0.686142    0.0724006  -0.302066  -0.654336 
 -0.582362   -0.0570904   0.010695   -0.735632   0.341065 
 -0.104062    0.704881   -0.248103    0.295724   0.585923 

julia> norm(A - Q*R) #Check an identity of the QR factorization
8.576118402884728e-16

julia> norm(Q'A - R) #Q'A is actually an Ac_mul_B in disguise
8.516860792899701e-16

julia> Base.ctranspose(Q::Base.LinAlg.QRCompactWYQ)=Q; #Reintroduce no-op fallback

julia> norm(ctranspose(Q)*A - R) #silently wrong 
4.554067975428161

Este exemplo mostra que só porque algo é um subtipo de Any não significa que você pode assumir que é um escalar e tem uma transposição autônoma. Também ilustra por que o problema dos pais no OP é tão difícil de resolver. Para um objeto Q não-array semelhante a uma matriz, Q' não tem nenhum significado real como uma operação de array, mas tem um significado algébrico inequívoco: expressões como Q'A são perfeitamente bem definidas. Outras pessoas que trabalham com matrizes e não com álgebra linear simplesmente querem permutados não recursivos e não se importam com não matrizes semelhantes a matrizes. No entanto, permanece o fato de que você não pode ter uma grafia consistente da semântica algébrica e de troca de eixo para todos os tipos.

Talvez eu esteja sendo estúpido, mas pensei que a comparação seria esta:

julia> A = rand(5,5); F = qrfact(A); R = F[:R]; Q = F[:Q]
julia> Base.ctranspose(Q::Any) = Q;
WARNING: Method definition ctranspose(Any) in module Base at operators.jl:300 overwritten in module Main at REPL[6]:1.
julia> norm(ctranspose(Q)*A - R) # still works fine
4.369698239720409e-16

Substituir transpose dessa maneira parece permitir transpose([ :x _=1:4 ]) - ou seja, o que eu postei. Eu teria pensado que, contanto que você implementasse transpose / ctranspose corretamente para tudo que precisa (por exemplo, QRCompactWYQ ), o substituto nunca seria chamado (desde uma chamada mais específica pode ser feito).

Seu código não está chamando o método ctranspose que você escreveu (você pode verificar isso com @which ). Ele está chamando um método alternativo diferente em Julia v0.5 (que não existe na v0.4) que está essencialmente fazendo ctranspose(full(Q)) . Esse outro fallback está correto, mas anula a própria razão de termos esse tipo Q sofisticado (de modo que a multiplicação por ele possa ser feita com precisão). Meu comentário de que os substitutos devem ser corretos ainda permanece.

Sim, desde que você implemente a transposição corretamente para tudo que
precisa, o recuo, claro, não faz mal. O mal é que você não
obterá um erro sem método se você esquecer de fazer isso, mas silenciosamente o erro
resultado.

Obrigado @toivoh por fazer clique para mim. Eu realmente aprecio as explicações.

Mas se você definir um fallback transpose(X::AbstractMatrix) e transpose(X::AbstractVector) funções, então presumivelmente você sempre obteria o resultado correto (mesmo se fosse lento ... chamando full por exemplo) não? E você sempre pode escrever uma função mais especializada para fazê-lo melhor / mais rápido. Então transpose(::Any) nunca deve causar erros silenciosos além dos "trocadilhos" mencionados anteriormente (ou seja, ao lidar com Complex números ... talvez outros casos de uso escalar que eu não conheço?)

Por razões históricas QRCompactWYQ <: AbstractMatrix , mas de acordo com # 987 # 10064 esta relação de subtipagem não está correta e deve ser removida.

Outro exemplo de não matriz semelhante a uma matriz é IterativeSolvers.AbstractMatrixFcn , que não é um subtipo de AbstractMatrix . Para esse tipo, os substitutos que você faz referência nunca seriam despachados, o que, novamente, era meu ponto principal.

Devemos continuar essa discussão em https://github.com/JuliaLang/julia/issues/13171. O problema é principalmente sobre vetores com número de elementos semelhantes.

Alguém da "álgebra linear da equipe" precisa se empenhar e se comprometer a fazer algo sobre isso por 0,6 ou será derrubado novamente.

Então, para reiniciar, qual é o plano real?

Minha linha de pensamento me leva a: transpose(v::AbstractVector) = TransposedVector(v) onde TransposedVector <: AbstractVector . A única coisa semântica que distinguirá TransposedVector de AbstractVector será como ele se comporta em * (e todos os A_mul_B s, \ , / , ...). Ou seja, é um decorador para determinar quais índices contrair abaixo de * (etc ...). A transposição seria um conceito de álgebra linear e se você quiser reorganizar um array de "dados", reshape e permutedims devem ser encorajados.

A única coisa semântica que irá distinguir TransposedVector de AbstractVector será como ele se comporta em *

Então v'==v mas v'*v != v*v' ? Embora isso possa fazer sentido, também parece potencialmente confuso.

Então v'==v mas v'*v != v*v' ? Embora isso possa fazer sentido, também parece potencialmente confuso.

IMO, isso é inevitável (embora possivelmente infeliz).

O dual de um vetor também é um vetor. A transposição ainda é uma estrutura de dados unidimensional com elementos bem definidos que devem ser capazes de obter, modificar, mapear, reduzir, etc.

A menos que separemos a álgebra linear das matrizes (por exemplo, fazer Matrix{T} um subtipo e um invólucro imutável de Array{T,2} , com mais métodos (específicos da álgebra linear) definidos), então não tenho certeza se é muita escolha que é consistente com suas propriedades de array e álgebra linear.

A única questão confusa (para mim) é se ele transmite como um vetor ou sobre sua segunda dimensão. Se seu tamanho for (1, n) então essa mudança não está realmente fazendo muito, exceto afirmar que é como uma matriz onde a primeira dimensão é conhecida por ter o comprimento 1 . A transposição de Vector{T} teria que permanecer Array{T,2} (ou seja, Matrix ...), entretanto a transposição disso poderia ser Vector novamente ( por exemplo, poderíamos ter v'' === v ).

É uma ideia melhor? Seria menos complicado e ainda melhoraria a semântica em relação à álgebra linear. EDIT: e se comporta de maneira diferente (escrito == conforme @martinholters mostra).

Para mim, ter TransposedVector{T <: AbstractVector{Tv}} <: AbstractMatrix{Tv} *) com size(::TransposedVector, 1)==1 mas transpose(::TransposedVector{T})::T soa como a abordagem mais sensata, mas tem havido tanto debate que provavelmente há algum bom argumento contra isso?

*) Eu sei que isso é sintaticamente inválido, mas espero que a semântica pretendida esteja clara.

Sim, ao brincar com ideias em código, acho que concordo com você @martinholters.

Comecei em https://github.com/andyferris/TransposedVectors.jl. Tudo isso pode ser alcançado com apenas uma pequena quantidade de pirataria de tipo e substituições de método de um pacote fora da base, e fiz um pacote compatível com Julia 0.5. Se der certo, podemos transportá-lo para a Base? (Ou então aprenda algumas lições).

Uma grande questão é se podemos viver sem um tipo CTransposedVector para conjugação complexa, ou como isso será resolvido.

Eu não usaria um CTransposedVector separado, porque ele combina dois conceitos (conjugação e remodelagem) em um objeto. É muito mais combinável manter esses dois conceitos implementados como entidades separadas. Por exemplo, com MappedArrays.jl é tão fácil quanto

conjview(A) = mappedarray((conj,conj), A)

e você também obtém um ótimo desempenho.

Certo, obrigado Tim. Eu estava pensando / esperando por algo assim - minha única preocupação era como você pode garantir que os dois invólucros sempre apareçam na ordem "correta", mas acho que o que implementei até agora pode lidar com isso. Além disso, precisaríamos suportar BLAS para todas essas combinações, que consegui evitar tocar até agora.

Uma coisa bonita é que uma mudança separada que padroniza conj para conjview seria independente para esta mudança (eu acho que ambos poderiam ser ajustados em pacotes independentes e eles comporiam juntos corretamente). Existe algum apetite para tornar conj uma visualização? Estamos aguardando que os não-isbits inline inmutáveis ​​tornem essas visualizações gratuitas? Ou não precisa esperar?

@andyferris : Acho que sua abordagem é boa - obrigado por levá-la adiante. Se a implementação funcionar bem, podemos apenas usar esse código no Base. Uma coisa a ter em mente é que também podemos querer TransposedMatrix para https://github.com/JuliaLang/julia/issues/5332. Além de 1 e 2 dimensões, não acho que a transposição faça sentido, então é um conjunto finito de tipos.

Agora que continuamos a conversão, mencionarei novamente que devemos tentar evitar um tipo de vetor transposto. Isso foi mencionado algumas vezes acima, mas duvido que alguém seja capaz de ler todos os comentários sobre esse assunto. A introdução de um tipo de transposição para vetores realmente complica as coisas quase sem benefício. Se você realmente acha que um vetor tem uma direção, torne-o uma matriz e as coisas simplesmente funcionarão. Não faz muito sentido ter vetores de álgebra linear diferentes de matrizes e, em seguida, introduzir um novo tipo de invólucro para fazer os vetores se comportarem como matrizes 1xn .

No momento, a assimetria é que x' promove x a uma matriz, enquanto A*x não promove x a uma matriz. Se as operações de álgebra linear fossem apenas para 2D, então A*x deveriam ser promovidos e x'x seria uma matriz 1x1 . Alternativamente, poderíamos deixar os vetores serem vetores e, portanto, também A*x ser um vetor, mas então x' deveria ser um noop ou um erro. A introdução de um vetor transposto exigirá muitas novas definições de método e o único benefício parece ser que x'x se torna um escalar.

Acho que a transposição da matriz é muito diferente. Isso nos permitirá nos livrar de todos os métodos Ax_mul_Bx e o comportamento não é discutível da mesma forma que o comportamento das transposições vetoriais.

Eu realmente não vejo como a introdução de um tipo TransposedVector causa mais problemas do que a introdução de um tipo TransposedMatrix.

Sim, o forte consenso em algum lugar no meio desta obra era que a transposição vetorial é estranha e que, se fosse implementada, definitivamente não deveria ser um subtipo de AbstractArray - eu até desaprovaria size inteiramente. Meu resumo acima enumera alguns dos desafios básicos de incluir um covetor - não menos importante, a notação (observe que eu recomendo usar a abordagem de Tim de mapear conj sobre a matriz em vez de incluí-la como um tipo parâmetro).

Mas havia um apelo ainda mais forte para que a transposição vetorial fosse simplesmente um erro. Se fosse esse o caso, acho que poderia realmente viver em um pacote.

Ter a transposição do vetor como um erro parece como jogar o bebê fora com a água do banho. Não ser capaz de escrever v' para transpor um vetor seria muito, muito chato. Eu realmente não entendo o que há de tão ruim em ter um tipo TransposedVector que se comporta como uma matriz de linha, além de como ele se multiplica com vetores e matrizes.

Depende de quantos comportamentos você deseja abordar com uma transposição vetorial. Se você simplesmente deseja v'' == v , então está ok para typeof(v') <: AbstractMatrix . Mas se você quiser as outras coisas sobre as quais falamos neste tópico, como typeof(v'v) <: Scalar ou typeof(v'A) <: AbstractVector , então precisa ser uma besta diferente e mais complicada.

A ideia de ter TransposedVector como uma espécie de vetor parece estar na raiz de muitos problemas. Parece que seria muito menos perturbador se fosse uma espécie de matriz e tivesse size(v.') == (1,length(v)) assim como temos agora. A única diferença seria que uma transposição dupla forneceria um vetor de volta e v'v produziria um escalar. Se alguém quiser tratar uma transposta como um vetor, poderá, já que length(v') dá a resposta certa e a indexação linear funciona conforme o esperado.

Eu concordo 110% com @StefanKarpinski. Eu brinquei em torná-lo um tipo de vetor, mas não faz muito sentido e é bastante complicado, pelos tipos de razões que foram discutidas anteriormente neste tópico.

A abordagem de fazer com que ele tenha o tamanho (1, n) significa em termos de comportamento de Array ele se comporta exatamente como agora. As _únicas_ operações que irão distingui-lo de 1-por-N Matrix são o comportamento sob ' , .' , * , \ . e / .

Nenhum desses são operadores do tipo "array". Eles existem puramente para implementar álgebra linear entre matrizes e vetores, e vêm diretamente do MATLAB ("laboratório de matrizes"). Este refinamento final simplesmente diz que size(tvec, 1) = 1 estaticamente para o compilador _and_ que eu quero v'' como v . (Eu meio que acho que é um pouco como StaticArray onde uma dimensão é fixa e a outra é dimensionada dinamicamente).

Se você simplesmente quer v '' == v, então está ok para typeof (v ') <: AbstractMatrix.

Direito.

Mas se você quiser as outras coisas sobre as quais falamos neste tópico, como typeof (v'v) <: Scalar ou typeof (v'A) <: AbstractVector, então ele precisa ser uma besta diferente e mais complicada.

Por quê? Não estamos quebrando nenhuma de suas propriedades semelhantes a array, então ele ainda pode ser um array. Qualquer chamada pontual funcionará como antes, e outras operações vetorizadas estão sendo removidas. Acho que * é "especializado" no conhecimento das várias formas de Vector e Matrix e que isso ocorre porque estamos tentando emular o comportamento da álgebra linear escrita. Fazer v' * v ser um escalar é _muito_ matemática padrão e é minha interpretação que a única razão de não ter sido assim desde o início é porque não é trivial implementar de uma forma consistente (e a inspiração que Julia tira MATLAB), mas Stefan poderia esclarecer isso. Tornar o produto interno um escalar é a única alteração importante aqui para falar (há outras coisas muito pequenas) - não vejo por que isso por si só o tornaria impróprio para ser um Array (há muitos tipos de Array que não possuem ' , .' , * , \ e / definidos _at all_ , notavelmente classificação 3+)

Um dos problemas que encontrei no ano passado foram ambigüidades; IIRC eles eram muito mais simples de resolver quando a transposição vetorial não era uma matriz.

Sim, estou um pouco preocupado com o quão profundo isso vai se tornar antes de eu terminar ... :)

Como uma observação geral, seria maravilhoso se / quando tornássemos Base menos monolítico e o incluíssemos em bibliotecas padrão. Ter um pacote ou módulo autocontido que apenas define AbstractArray e Array tornaria este tipo de desenvolvimento mais simples.

Qual foi exatamente o problema com a proposta de @Jutho ? Não poderia * automaticamente (conjugado) o vetor à esquerda?

Se temos * a multiplicação da matriz, ' a transposição da matriz (conjugada) (deixa os vetores inalterados) e dois operadores promote que adiciona um singleton final e demote que remove um singleton à direita, então podemos construir um aplicativo certo *: (n,m) -> (m,) -> (n,) , um aplicativo esquerdo *: (n,) -> (n,m) -> (m,) , um produto escalar *: (n,) -> (m,) -> (,) e um produto externo ×: (n,) -> (m,) -> (n,m) tal que:

(*)(A::AbstractMatrix, v::AbstractVector) = demote(A*promote(v))
(*)(u::AbstractVector, A::AbstractMatrix) = demote((promote(u)'*A)')
(*)(u::AbstractVector, v::AbstractVector) = demote(demote(promote(u)'*promote(v)))
(×)(u::AbstractVector, v::AbstractVector) = promote(u)*promote(v)'

Não tenho certeza se algum dia precisaria transpor um vetor com esses operadores. Estou esquecendo de algo?

Edit: Não exatamente a proposta de @Jutho .

@Armavica , não tenho certeza se foi exatamente minha proposta atribuir este significado a * mas sim a um novo operador (unicode) , que atualmente é equivalente a dot . Além disso, não acho que você seria capaz de implementar a abordagem promote e demote maneira estável.

O purista em mim concorda com @andreasnoack que a transposição de um vetor em si deve ser evitada (eu pessoalmente prefiro escrever dot(v,w) sobre v'w ou v'*w qualquer momento), mas também posso apreciar a flexibilidade de fazê-lo funcionar da maneira proposta por @andyferris . Portanto, não vou acrescentar mais nada a esta discussão e apoiar qualquer tentativa sã de obter uma implementação real em execução.

Certo, @Jutho. Curiosamente, os dois não são mutuamente exclusivos: não ter transposição no vetor definido por padrão significa que essa funcionalidade pode residir legitimamente em um pacote separado, módulo ou "biblioteca padrão" (ou seja, sem "pirataria de tipos").

(Eu pessoalmente prefiro escrever ponto (v, w) em vez de v'w ou v '* w a qualquer momento)

Jutho - por outro lado, aposto que você não escreve | ψ> ⋅ | ψ> em um pedaço de papel ou quadro branco, mas sim <ψ | ψ> (e a mesma analogia vale para como desenhamos produtos internos com diagramas de rede de tensores).

Claro que eu adoraria ver a notação do freio de Dirac como a formulação padrão da álgebra linear em toda a matemática ou mesmo nas ciências e, por extensão, em julia, mas provavelmente isso não vai acontecer.

Agora você me força a divagar, o que eu não faria mais. Eu certamente concordo em preferir <ψ | ψ> para o produto interno, mas isso é independente do fato de que eu não penso em <ψ | como o conjugado hermitiano de | ψ>. A conjugação hermitiana é definida para operadores. O isomorfismo natural entre os vetores e seus vetores duais associados (covetores, funcionais lineares, ...) é conhecido como teorema da representação de Riesz , embora o primeiro seja obviamente equivalente à conjugação hermitiana ao interpretar vetores de comprimento n como mapas lineares de C a C ^ n, ou seja, como matrizes de tamanho (n,1) .

Claro que eu adoraria ver a notação do freio de Dirac como a formulação padrão da álgebra linear em toda a matemática ou mesmo nas ciências e, por extensão, em julia, mas provavelmente isso não vai acontecer.

Ri muito

Agora você me força a divagar, o que eu não faria mais.

Desculpe ... Eu realmente não deveria ter mencionado isso ... :)

Eu não penso em <ψ | como o conjugado hermitiano de | ψ>. A conjugação hermitiana é definida para operadores.

Verdade, eu não tinha considerado isso.

O isomorfismo natural entre vetores e seus vetores duais associados (covetores, funcionais lineares, ...) é conhecido como teorema da representação de Riesz, embora o primeiro seja, obviamente, equivalente à conjugação de Hermit ao interpretar vetores de comprimento n como mapas lineares de C a C ^ n, ou seja, como matrizes de tamanho (n, 1).

Estou tentando não divagar (mas sou ruim nisso), mas acho que entendemos um pouco dessa última parte, já que estamos associando AbstractVector a um 1D Array . Em outras palavras, AbstractVector não significa "vetor abstrato" em um sentido matemático, mas significa "os elementos numéricos de um vetor com alguma base predefinida". O MATLAB saiu dessa facilmente porque os vetores eram do tamanho (n,1) .

A título de reflexão, como você chama o operador (antilinear) que pega todos os tensores da forma |a>|b><c| e os mapeia para |c><a|<b| ? Eu sempre empacotei isso junto com a conjugação vetorial dupla e operador padrão, como "conjugação hermitiana", mas talvez isso seja muito blasé.

Tive uma conversa com @alanedelman que cristalizou algumas coisas sobre minha posição no # 4774:

  • introduzir Covector ou RowVector type;
  • para operações algébricas lineares ( * , ' , .' , / , \ , talvez norm ? outros? ) este tipo atua como um covetor na álgebra linear;
  • para operações de array (todo o resto), este tipo atua como um array 1 × n bidimensional;
  • em particular, size(v') de um covector é (1, length(v)) .

Esta abordagem requer a classificação de todas as funções genéricas em Base como algébricas lineares ou não. Em particular, size é uma função de matriz e não faz sentido no domínio da álgebra linear, que essencialmente tem apenas vetores (que não têm "forma", apenas uma cardinalidade de dimensões), transformações lineares (que pode acontecer de ser representado por matrizes bidimensionais) e escalares. Um exemplo específico que surgiu foi cumsum , que atualmente opera em matrizes bidimensionais como se um argumento de dimensão 1 fosse fornecido. Isso é inconsistente com sum , que em vez de padronizar para um argumento de dimensão de 1 retorna a soma de todo o array. Também evita que cumsum opere em covetores de uma maneira correspondente a como opera em vetores. Acho que cumsum deve operar em matrizes gerais somando cumulativamente na ordem N-dimensional da coluna principal. De forma mais geral, os argumentos de dimensão não devem ser padronizados como 1.

Eu ficaria um pouco preocupado se não houvesse uma interface para identificar covetores. A maioria das operações, por exemplo, size e ndims, diriam que eles são exatamente como outros arrays 2d ou 1d. Apenas tentando, por exemplo, um produto escalar você vê a diferença. AFAICT, você teria que verificar se o objeto é um RowVector, mas é muito raro exigir que um objeto tenha um tipo específico para ter uma certa propriedade geral.

@StefanKarpinski Concordo com tudo isso. Especialmente porque as funções são operações de "matriz" ou operações de "álgebra linear".

Comecei com um tamanho = (1, n) TransposedVector aqui, que é exatamente como você disse. Demorei um pouco para obter uma boa cobertura dos testes e de todas as combinações * , \ , / para cada c e t possíveis método e os métodos mutantes, evitando ambigüidades com outros métodos na base. É um trabalho lento, mas sistemático, e a partir disso pensei que poderíamos puxá-lo para a base quando estiver pronto (possivelmente renomeando as coisas).

Há uma distinção com Covector onde realmente deveria ser a transposta conjugada (ou uma transformação ainda mais geral, no caso geral!), Enquanto um RowVector ou TransposedVector é conceitualmente mais simples.

@JeffBezanson Existe algo que podemos fazer com indices() para ter uma dimensão "singleton"?

mas é muito raro exigir que um objeto tenha um tipo específico para ter uma certa propriedade geral.

Certo, seria legal se isso fosse uma característica ou algo assim. Eu esperava que pudéssemos separar a álgebra linear de matrizes de forma mais geral, mas isso é uma grande mudança. e provavelmente não poderia ser organizado sem uma boa sintaxe de traços implementada no Julia. Acho que aqui o problema é que estamos mapeando 3 coisas (vetores, covetores e matrizes) em dois tipos ( AbstractArray{1} e AbstractArray{2} ), e então um deles (covetores) se torna um subtipo especial do outro.

Eu teria colocado AbstractTransposedVector no pacote se pudesse pensar em um lugar onde alguém precisaria de algo diferente da implementação básica do "invólucro".

@JeffBezanson : Não entendo sua preocupação. A ideia é que ele se comporta exatamente como uma matriz abstrata 1 × n 2d, exceto para operações de álgebra linear, para as quais se comporta como o espaço dual de vetores de coluna (que é isomórfico a matrizes abstratas 1 × n 2d). Se quiser verificar se há um covector, você pode verificar o tipo, por exemplo, enviando para ele.

Uma atualização sobre minhas tentativas de lidar com isso:

TransposedVectors.jl agora é, acredito, "recurso completo". Ele contém todo o maquinário necessário para fazer o que @StefanKarpinski falou aqui - a transposição de um vetor é um invólucro de um vetor que se comporta como um array abstrato bidimensional de tamanho 1xn - mas para operações de álgebra linear ( * , / , \ , ' , .' e norm ) se comporta como um vetor linha (ou vetor duplo).

Você pode conferir assim:

julia> Pkg.clone("https://github.com/andyferris/TransposedVectors.jl")
...

julia> using TransposedVectors
WARNING: Method definition transpose(AbstractArray{T<:Any, 1}) in module Base at arraymath.jl:416 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/TransposedVector.jl:28.
WARNING: Method definition ctranspose(AbstractArray{#T<:Any, 1}) in module Base at arraymath.jl:417 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/TransposedVector.jl:29.
WARNING: Method definition *(AbstractArray{T<:Any, 1}, AbstractArray{T<:Any, 2}) in module LinAlg at linalg/matmul.jl:86 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:9.
WARNING: Method definition At_mul_B(AbstractArray{#T<:Real, 1}, AbstractArray{#T<:Real, 1}) in module LinAlg at linalg/matmul.jl:74 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:37.
WARNING: Method definition Ac_mul_B(AbstractArray{T<:Any, 1}, AbstractArray{T<:Any, 1}) in module LinAlg at linalg/matmul.jl:73 overwritten in module TransposedVectors at /home/ferris/.julia/v0.5/TransposedVectors/src/mul.jl:64.

julia> v = [1,2,3]
3-element Array{Int64,1}:
 1
 2
 3

julia> vt = v'
1×3 TransposedVectors.TransposedVector{Int64,Array{Int64,1}}:
 1  2  3

julia> vt*v
14

julia> vt*eye(3)
1×3 TransposedVectors.TransposedVector{Float64,Array{Float64,1}}:
 1.0  2.0  3.0

Pode haver algumas degradações de desempenho - o benchmarking seria útil. Em alguns casos, fará menos cópias (a transposição é preguiçosa) e em outros casos fará mais cópias (às vezes uma cópia conjugada de um vetor (nunca uma matriz) é criada em Ac_mul_Bc por exemplo). E o wrapper em si tem um pequeno custo até colocarmos campos mutáveis ​​embutidos em imutáveis ​​(felizmente para mim, isso já funciona bem com StaticArrays.jl : smile :). Há também o problema de garantir que seja algum tipo de StridedArray quando apropriado.

Se as pessoas gostarem dessa abordagem, podemos ver como fazer um PR em breve para migrar o código para Base (o pacote já tem testes de unidade e todas as ambigüidades resolvidas). (também, @jiahao mencionou que deseja experimentar uma versão de array abstrato unidimensional de um vetor dual, não tem certeza se algum progresso foi feito?)

As pessoas acham que tal PR faria algum sentido na v0.6 sem um tipo de invólucro para matrizes transpostas? Enquanto os vetores transpostos são uma mudança semântica, as matrizes transpostas são mais uma otimização, então suponho que elas poderiam entrar separadamente. Claro, pode parecer "estranho" se algumas transposições forem opiniões e outras cópias - opiniões? Outro ponto a considerar é que, uma vez que ambas as matrizes e vetores transpostos estão inseridos, muito trabalho precisa ser feito para remover todas as funções At_mul_Bt , substituídas por métodos em * , para completar o transição (embora a simplificação seja recompensadora!) - Duvido que alguém seja capaz ou deseje fazer tudo isso até o final deste mês ...

Bom trabalho, @andyferris! Você experimentou um invólucro Conjugate preguiçoso genérico?

@andyferris Eu chutei os pneus, e gosto bastante disso. Parece estritamente uma melhoria, e espero que os problemas de desempenho possam ser resolvidos da forma como os encontramos. Vamos ver o que os outros têm a dizer.

Obrigado rapazes :)

Você experimentou um invólucro Conjugate preguiçoso genérico?

Ainda não, embora possa ser possível sem muito esforço, mas para ser honesto, eu estava preocupado em não terminar tudo isso até o final de dezembro. Olhando para o futuro, eu estava considerando que no final poderíamos ter os seguintes tipos "unionall" para despachar para álgebra linear:

  • AbstractVector
  • Conjugate{V where V <: AbstractVector} (ou Conj{V} , talvez até ConjArray ? Claro que aceitaria matrizes de qualquer dimensão)
  • TransposedVector (ou RowVector ?)
  • TransposedVector{Conjugate{V where V <: AbstractVector}}
  • AbstractMatrix
  • Conjugate{M where M <: AbstractMatrix}
  • TransposedMatrix (ou simplesmente Transpose{M} ?)
  • TransposedMatrix{Conjugate{M where M<:AbstractMatrix}}

(além de todas as características fornecidas do armazenamento subjacente, como o que agora chamamos de DenseArray e StridedArray - espero que # 18457 torne um pouco mais fácil expressá-las também).

Além de nomear, isso soa como um plano razoável?

Quanto à bicicleta imediata, para um PR para Base do que está atualmente no pacote, preferimos TransposedVector , RowVector , um envoltório genérico Transpose para vetores e matrizes , ou alguma outra coisa?

Acho que o comportamento fornecido pela implementação de @andyferris é muito bom e uma melhoria. Mas deixe-me tentar expressar minha preocupação um pouco melhor.

O problema é definir novos tipos de array. Por exemplo, DArray é um subtipo de AbstractArray , então DArray também pode ser AbstractVector ou AbstractMatrix . Idealmente, estenderíamos apenas a distinção Vetor / Matriz para Linha / Coluna / Matriz, de modo que DArray pudesse ser AbstractRow , AbstractCol ou AbstractMatrix . A hierarquia de tipo seria algo como

AbstractArray
    AbstractVector
        AbstractRowVector
        AbstractColumnVector
    AbstractMatrix
    ...

Estava conversando com @jiahao sobre isso ontem.

Pode haver alguma precedência aqui, em que nem todos AbstractVector s podem ser usados ​​como colunas, por exemplo:

julia> isa(1.0:10.0, AbstractVector)
true

julia> randn(10,10) * 1.0:10.0
ERROR: MethodError: no method matching colon(::Array{Float64,2}, ::Float64)

que poderia resolver os problemas que tive em https://github.com/JuliaLang/julia/issues/4774#issuecomment -59428215

Isso é apenas faltar parênteses em torno do intervalo?

Isso é apenas uma questão de precedência:

julia> randn(10,10) * (1.0:10.0)
10-element Array{Float64,1}:
 -22.4311
  ⋮

Concordo que a solução ideal aqui parece exigir características, mas não acho que isso deva impedir o trabalho de

ah, bom ponto. Adoraria ter precedência de espaço em branco incorreta ser um erro de sintaxe, semelhante ao Fortress.

O grupo MIT apontou que uma maneira de implementar a hierarquia de tipo que descrevi acima é adicionar um parâmetro de tipo à hierarquia de array:

abstract AbstractArray{T,N,row}

type Array{T,N,row} <: AbstractArray{T,N,row}
end

typealias AbstractVector{T} AbstractArray{T,1}
typealias AbstractRowVector{T} AbstractArray{T,1,true}
typealias AbstractColVector{T} AbstractArray{T,1,false}
typealias AbstractMatrix{T} AbstractMatrix{T,2}

typealias Vector{T} Array{T,1,false}
typealias Matrix{T} Array{T,2,false}

Eu não descobri todas as implicações disso - provavelmente o pior de tudo é que Array{Int,1} se torna um tipo abstrato - mas isso parece fazer com que a hierarquia de tipos e as abstrações necessárias estejam exatamente certas.

Pelo que vale a pena, este é exatamente um caso de uso para supertipos parametrizados incompletamente; eles podem se tornar parâmetros padrão implícitos.

abstract AbstractArray{T,N,row}

type Array{T,N} <: AbstractArray{T,N}
end

isrow{T,N,row}(::AbstractArray{T,N,row}) = row
isrow{T,N}(::AbstractArray{T,N}) = false

julia> isrow(Array{Int,2}())
false

Claro, isso torna o despacho um pouco confuso ... e pode não valer a pena apoiá-lo. É apenas uma bola de cuspe.

Para mim, isso parece equivalente a definir

type Array{T,N} <: AbstractArray{T,N,false}
end

O que podemos querer aqui é algum tipo de "parâmetros de tipo padrão", de modo que fazer o tipo Array{X,Y} onde X e Y não têm variáveis ​​livres na verdade dá Array{X,Y,false} .

Outra ideia: preservar a notação de Householder para produtos internos v'v e o covector esquerdo multiplicar v'A tornando o infixo ' seu próprio operador em vez de analisá-los como v'*v e v'*A . Juntamente com v' a identidade, v*v um erro e v*A um erro, isso pode fornecer muito do que é desejado. Você teria que escrever produtos externos como outer(v,v) .

Em tal esquema, v' seria um noop ou mesmo um erro - uma vez que não há razão para transpor um vetor em tal esquema, só faria sentido para uma matriz.

@JeffBezanson Eu concordo que ter um AbstractRowVector seria o melhor (mas eu honestamente não consigo pensar em um caso de uso, então não o implementei no pacote). Também gostaria de salientar que isso pode ser um subtipo de AbstractMatrix . A razão pela qual me movi nessa direção é que broadcast parece ser mais um conceito central para Julia do que álgebra linear, e as pessoas esperam que um vetor linha seja, bem, uma linha!

Claro que ter RowVector <: AbstractMatrix é um uso infeliz da terminologia! Acho que isso resulta do mesmo nome que matrizes 2D e matrizes abstratas.

Já disse isso antes, muito acima, mas, como esse problema é tão longo, vou reafirmá-lo: em uma linguagem genérica como Julia, a propriedade "array of data" deve ser a principal consideração para AbstractArray . Responder como você deseja que broadcast se comporte para um vetor "transposto" informa se ele é 1D ou 2D. Se você quiser pensar em termos de linhas e colunas, então 1 x n vetores de linha fazem mais sentido. Se você quiser considerar vetores duais, então 1D faz mais sentido - e estou feliz com isso também! Mas tomar o dual de um vetor é mais complicado do que remodelar os dados (por exemplo, temos que pelo menos suportar a conjugação).

Eu acho que a imagem da "linha" corresponderia às expectativas dos programadores "típicos", enquanto as pessoas com treinamento matemático avançado possivelmente obteriam melhor uso dos vetores duais, pois é uma abstração melhor (embora eu tenha certeza de que seriam capazes de simpatizar com aqueles que possuem apenas conhecimentos básicos de álgebra linear). Então - a qual público Julia se dirige - pessoas com mais sutileza matemática do que muitos usuários típicos do MATLAB, ou pessoas com menos?

(Minha opinião era que como Julia pretendia ser uma linguagem de programação "genérica", o último público era o alvo).

Já que estamos discutindo possíveis árvores de tipo - no futuro, com Buffer e "listas" redimensionáveis, estou imaginando uma árvore abstrata de interfaces que segue algo nas linhas de

AbstractArray{T,N} # interface includes broadcast, Cartesian, getindex, setindex!, etc.
    AbstractArray{T,1}
        AbstractList{T} # resizeable, overloaded with `push!` and so-on
        AbstractVector{T} # non-resizeable, overloaded for *, /, \, ', .', etc
    AbstractArray{T,2}
        AbstractRowVector{T} # non-resizeable, overloaded for *, /, \, ', .', etc
        AbstractMatrix{T} # non-resizeable, overloaded for *, /, \, ', .', etc

Claro, poderíamos igualmente ter AbstractDualVector{T} <: AbstractArray{T,1} vez de AbstractRowVector .

Ter um tipo de Array flexível e concreto para caber em todos esses seria difícil (e talvez desnecessário). As características certamente nos permitiriam expressar essas diferenças nas interfaces com suporte mais facilmente.

Ter AbstractVector sendo um C ++ std::vector e um vetor de álgebra linear pareceu um pouco atrevido para mim :) (especialmente porque a interface de array significa que nunca pode ser verdadeiramente um "vetor abstrato" sentido de álgebra linear (por exemplo, base livre))

Sim, seria bom separar o comportamento de redimensionamento. Estou a bordo para isso.

Essa hierarquia de tipo parece implicar que teríamos tipos concretos separados de "matriz" e "matriz 2-d" em Base. Poderíamos tentar contornar isso, por exemplo, fazendo com que o construtor de Array{T,2} realmente retorne algum outro tipo de matriz, mas parece muito feio. Talvez eu esteja entendendo mal.

Essa hierarquia de tipo parece implicar que teríamos tipos concretos separados de "matriz" e "matriz 2-d" em Base.

Certo ... Acho que para fazer isso bem, precisaríamos de traços. Lembre-se de que a chamei de "árvore abstrata de interfaces". Tentar fazer isso no momento exigiria algo feio como você disse. Mas provavelmente poderíamos inserir AbstractList <: AbstractVector (e mover os métodos associados) assim que Buffer estiver pronto.

No momento, a conexão entre as interfaces suportadas e a árvore de tipos está bem frouxa. É difícil descobrir no momento se um array (abstrato) é mutável (ou seja, suporta setindex! ), se é redimensionável, strided só funciona para tipos definidos em Base , etc. Isso o torna difícil para os usuários fornecerem uma função com métodos que funcionem e sejam eficientes com uma ampla gama de entradas (esta é minha experiência com StaticArrays ).

Uma ideia para a conversa sobre regiões matemáticas da hierarquia de tipos e papéis razoáveis ​​para abstrações parametrizadas. Quando possível, é uma boa política para Julia simplificar e iluminar qualquer separação de como o código é escrito para fazer o que a experiência pretende e o que é intenção expressa de forma experiente.

Uma convenção que podemos escolher para usar torna a prática comum usar um tipo abstrato diferente de Any para formar uma região ontotopológica em meio à qual tipos concretos concomitantes acham o repouso compartilhado fácil. Isso traria, a um custo muito baixo, maior sensibilidade multi-contextual - entender alguma parte da hierarquia de tipo ajuda a abordar outras partes.

A meu ver, é um portador de relacionamento generalizado. Uma abstração elucidativa ilumina como uma vizinhança cointendendo concreções, as semelhanças geradoras que constroem. Com parametrizações carregando a clareza simples da intuição, Julia consegue que muitos realizem mais com menos tempo sem sentido.

OK, mais comentários sobre TransposedVectors serão apreciados. Acho que está ficando bastante sólido e pronto para ser transformado em RP, mas há alguns problemas relevantes que listei aqui .

(por exemplo: RowVector um nome melhor do que TransposedVector ? É [1 2 3] a RowVector ou Matrix ? O que é indices(row, 1) ?)

+1 para RowVector

Em 20 de dezembro de 2016, 7:01, "Andy Ferris" [email protected] escreveu:

OK, mais comentários sobre TransposedVectors serão apreciados. eu sinto
está ficando bastante sólido e pronto para ser transformado em RP, mas há
alguns problemas relevantes que listei aqui
https://github.com/andyferris/TransposedVectors.jl/issues .

(por exemplo: RowVector é um nome melhor do que TransposedVector? É [1 2
3] um RowVector ou uma Matrix? O que são índices (linha, 1)?)

-
Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/JuliaLang/julia/issues/4774#issuecomment-268170323 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAm20YYqsXmprI23GgI5PYyWStpTOq5qks5rJ309gaJpZM4BMOXs
.

Eu realmente gostaria de manter a convenção linha / coluna fora disso, e acho que parece estranho ter Vector e RowVector (ou mesmo ColVector e RowVector ). +1 para TransposedVector ou DualVector

@felixrehren Acho que DualVector deve ter uma semântica diferente da que implementei, principalmente sendo unidimensional (matematicamente, o dual de um vetor é um vetor) e ter outras propriedades de dualidade (por exemplo, conjugação complexa) . O que é bom, mas achei um pouco mais difícil de implementar e um pouco menos compatível com versões anteriores.

O nome TransposedVector está ok. Mas é um pouco longo. Além disso, meio que sugere que um objeto desse tipo só pode ser obtido pela transposição de Vector . Mas presumo que você também poderia fazer TransposedVector por outros meios, digamos, extraindo uma linha de uma matriz.

Acho que RowVector seria um bom nome - é conciso, preciso e intuitivo. @felixrehren , acho que a imagem da linha / coluna é útil. Provavelmente é até inevitável, pois sempre que você faz concatenação ou outras operações comuns de array (além da multiplicação), você precisa pensar sobre de que maneira o vetor é orientado.

DualVector também não é ruim, mas CoVector soaria menos formal.

O PR que ameacei anteriormente foi submetido em # 19670. Eu fui com RowVector por enquanto.

Mas presumo que você também possa fazer um TransposedVector por outros meios, digamos, extraindo uma linha de uma matriz.

Na verdade, esse é um ponto crítico - ainda não pensei em uma ótima sintaxe para isso. Alguma ideia?

Mas presumo que você também possa fazer um TransposedVector por outros meios, digamos, extraindo uma linha de uma matriz.

Na verdade, esse é um ponto crítico - ainda não pensei em uma ótima sintaxe para isso. Alguma ideia?

Embora no início parecesse atraente ter matrix[scalar,range] (e outras construções semelhantes) gerando um vetor de linha, isso seria um afastamento significativo da semântica de indexação atual para arrays multidimensionais, e casos especiais me deixam desconfiado.

Em vez disso, gostaria de ver RowVector (e Vector para esse assunto) converter qualquer tipo iterável no tipo apropriado de vetor. Então você poderia fazer algo como RowVector(matrix[scalar,range]) que seria bastante claro e não perturbaria o comportamento atual da indexação de array.

Certo, a linha i th pode ser extraída em uma forma de linha por A[i,:].' , atualmente na v0.5 e continuaria a fazê-lo no futuro (para fazer um RowVector ou Transpose{V<:AbstractVector} ou qualquer outra coisa que acertarmos eventualmente (veja aqui a discussão em andamento)). Talvez seja essa a resposta.

Que tal apenas adicionar duas novas funções ao Base?
row(A,i) e col(A,i)
Este último não é necessário, mas apenas para simetria. É conciso, claro e com tantos caracteres quanto A[i,:].'

@benninkrs isso faz sentido. Em contraste, minha interpretação intuitiva é a de álgebra linear, em que um vetor não tem orientação alguma. Todos os pontos de vista sobre isso são razoáveis, eu só não gosto de Vector e RowVector juntos porque a nomenclatura parece que mistura um paradigma abstrato e um paradigma concreto.

Neste ponto, acho que precisamos ir com algo baseado na implementação de @andyferris ou decidir não levar a sério a transposição vetorial. Como exemplo de alternativa, aqui está uma abordagem que trata os piores sintomas: deixe v' como está atualmente - ou seja, produzindo uma matriz de linha - mas analise a'b como um operador infixo com precedência logo abaixo multiplicação. Em outras palavras, teríamos os seguintes comportamentos:

  1. v' é uma matriz de linha (ctranspor)
  2. v'v é um escalar (produto
  3. v'*v é um vetor de 1 elemento (matriz de linha * vetor)
  4. v*v' é uma matriz de produto externa (vetor * matriz linha)
  5. v'A é uma matriz de linha ("vecmat")
  6. v'*A é uma matriz de linha (matmat)
  7. v'A*v é um escalar (matvec A * v, em seguida, produto escalar)
  8. (v'A)*v é um vetor de 1 elemento (vecmat e matvec)
  9. v'*A*v é um vetor de 1 elemento (matmat e depois matvec)
  10. v'' é uma matriz de coluna (vetor ctranspor, depois matriz ctranspor)

Na configuração atual, 2 e 3 são equivalentes e 7, 8 e 9 são equivalentes, que essa alteração quebra. Mas, crucialmente, os itens em negrito são aqueles que as pessoas geralmente procuram, pois são os mais curtos e convenientes das formas semelhantes - e todos fazem o que gostaríamos que fizessem. Sem novos tipos, apenas um novo operador de infixo. A principal desvantagem é 10 - v'' ainda é uma matriz de coluna. Indiscutivelmente, isso poderia ser visto como um benefício, uma vez que o postfix '' é o operador para transformar um vetor em uma matriz de coluna.

Dando um passo para trás, acho que o que aprendemos é que sem funcionalidade adicional de up-down ou rotulagem de dimensão ou tratando ≤ 2 dimensões como fungíveis como o Matlab faz, os arrays multidimensionais não podem ser feitos para se encaixar perfeitamente com a álgebra linear. Então, o que resta é uma questão de conveniência - podemos permitir que as pessoas expressem operações comuns de álgebra linear em vetores e matrizes de maneira conveniente, sem complicar demais os arrays? Não estou totalmente convencido dessa abordagem, mas acho que devemos considerar seriamente essa e outras abordagens que tratam da conveniência sintática sem bagunçar demais nossa hierarquia de tipos de array.

Outra abordagem seria analisar o infixo a'b especialmente (logo abaixo de * ) e fazer v' retornar um vetor conjugado. De forma mais geral, o postfix A' poderia conjugar um array e reverter preguiçosamente suas dimensões, enquanto A.' iria apenas reverter preguiçosamente as dimensões de um array, agindo assim como a identidade nos vetores. Neste esquema, a lista de propriedades pode ser a seguinte:

  1. v' é um vetor (conjugado)
  2. v'v é um escalar (produto
  3. v'*v é um erro (recomende v'v para o produto interno e outer(v,v) para o produto externo) †
  4. v*v' é um erro (recomende v'v para o produto interno e outer(v,v) para o produto externo) †
  5. v'A é um vetor (vecmat)
  6. v'*A é um erro (recomendo v'A para vecmat)
  7. v'A*v é um escalar (matvec A * v, em seguida, produto escalar)
  8. (v'A)*v é um erro (recomende v'v para o produto interno e outer(v,v) para o produto externo) †
  9. v'A'v é um escalar ( v'(A'v) - conjugado matvec e depois produto interno)
  10. v'' é um vetor ( v'' === v e v.' === v )

Agora que escrevi todas essas propriedades, esta pode ser a opção preferível: todos os casos de erro realmente servem para ajudar as pessoas a descobrir e usar sintaxes perferidas e, é claro, tem a propriedade v'' === v desejável (e se encaixa perfeitamente com .' sendo um operador de reversão de dimensão genérica). Ter sintaxes muito semelhantes que são apenas sutilmente diferentes é provavelmente mais confuso.

† Poderíamos potencialmente detectar isso no tempo de análise para erros mais precisos, correndo o risco de fornecer erros para casos em que o código do usuário sobrecarregou ' e * para que essas operações funcionem. Acredito que pode ser necessário ter um invólucro de conjugado lento para fazer essas recomendações corretas, mas precisamos disso de qualquer maneira para # 5332.

Dando um passo para trás, acho que o que aprendemos é que sem funcionalidade adicional de up-down ou rotulagem de dimensão ou tratando ≤ 2 dimensões como fungíveis como o Matlab faz, os arrays multidimensionais não podem ser feitos para se encaixar perfeitamente com a álgebra linear. Então, o que resta é uma questão de conveniência - podemos permitir que as pessoas expressem operações comuns de álgebra linear em vetores e matrizes de maneira conveniente, sem complicar demais os arrays? Não estou totalmente convencido dessa abordagem, mas acho que devemos considerar seriamente essa e outras abordagens que tratam da conveniência sintática sem bagunçar demais nossa hierarquia de tipos de array.

: 100:

Fazer explicitamente pós-fixados ' e .' operações de array genérico (em vez de álgebra linear) evita dobrar os tipos de armazenamento e álgebra linear e deixa a porta aberta para frameworks que envolvem menos compromissos. Nesse ínterim, a capacidade dessas operações de simular a notação de Householder na maioria dos casos comuns deve fornecer a maior conveniência desejada. Também menos código e complexidade. Melhor!

Um problema de v.' ser autônomo é que A .+ v.' mudaria o significado ao adicionar os valores de v a cada coluna de A para adicionar os valores às linhas de A . Isso geralmente tornaria mais difícil fazer operações semelhantes a linhas com vetores e definitivamente precisaria de um ciclo completo de depreciação para evitar silenciosamente fazer o código fazer a coisa errada (em casos como este em que A é quadrado).

Neste ponto, acho que precisamos ir com algo baseado na implementação de @andyferris ou decidir não levar a sério a transposição vetorial.

Sei que o prazo para a v0.6 está quase chegando, mas eu alertaria contra jogar fora o bebê com a água do banho. Neste estágio, parece que as mencionadas RowVector mais visualizações para transposição de matriz e conjugação de matriz nos permitirão obter:

  • IMO, tipos de álgebra linear um pouco mais razoáveis ​​(não estamos negando a existência de vetores duais como agora, embora um vetor linha possa ser visto como um caso especial de vetor dual)
  • v'' === v
  • Algumas das coisas na lista de Stefan como v1'v2 é um produto escalar
  • Semântica de array quase compatível com versões anteriores - por exemplo, o resultado de size(v') permanece inalterado, mas temos v'' como unidimensional
  • Wrappers conj e transpor preguiçosos podem aumentar o desempenho em certas circunstâncias e ser úteis de qualquer maneira
  • Remoção de todas as funções Ac_mul_Bc em favor de * e A_mul_B! apenas (e da mesma forma para \ , / ).

Fazer tudo isso funcionar por Array honestamente não exigiria muito esforço (para mim, o problema é encontrar tempo nesta época do ano e perseguir todos os outros tipos que temos em nosso conjunto de álgebra linear). E o último ponto seria um suspiro de alívio.

Por outro lado - IMHO essas regras parecem complicar um pouco a análise e podem ser um pouco confusas e / ou surpreendentes como elas compõem ' com * (3, 4, 6 e 8 funcionariam com RowVector ).

E sim, teríamos que descontinuar v.' ou algo assim para destacar possíveis bugs, ponto em que quase parece melhor tornar v.' um erro de método ausente (simplesmente não suportamos linha / vetores duplos, mas não impedirá um pacote de fazê-lo se desejar)

19670 está parecendo pronto ou próximo disso, se houver apetite para introduzir algo furtivamente na versão 0.6.

BAM

Woot.

Este foi o nosso tópico mais longo?

Não, # 11004 tem mais

Desculpa. Você está certo, eu deveria ter especificado o tópico de questão

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

dpsanders picture dpsanders  ·  3Comentários

tkoolen picture tkoolen  ·  3Comentários

m-j-w picture m-j-w  ·  3Comentários

sbromberger picture sbromberger  ·  3Comentários

iamed2 picture iamed2  ·  3Comentários