Multiplicar Quaternions

13

Escreva uma função ou programa nomeado que calcule o produto quaternion de dois quaternions. Use o mínimo de bytes possível.

Quaternions

Quaternions são uma extensão dos números reais que ampliam ainda mais os números complexos. Em vez de uma única unidade imaginária i, os quaternions usam três unidades imaginárias i,j,kque satisfazem os relacionamentos.

i*i = j*j = k*k = -1
i*j =  k
j*i = -k
j*k =  i
k*j = -i
k*i =  j
i*k = -j

(Também há tabelas na página da Wikipedia .)

Em palavras, cada unidade imaginária se enquadra em -1e o produto de duas unidades imaginárias diferentes é o terceiro restante, +/-dependendo se a ordem cíclica (i,j,k)é respeitada (isto é, a regra da mão direita ). Então, a ordem da multiplicação é importante.

Um quaternion geral é uma combinação linear de uma parte real e das três unidades imaginárias. Então, é descrito por quatro números reais (a,b,c,d).

x = a + b*i + c*j + d*k

Assim, podemos multiplicar dois quaternions usando a propriedade distributiva, tendo o cuidado de multiplicar as unidades na ordem correta e agrupando os termos no resultado.

(a + b*i + c*j + d*k) * (e + f*i + g*j + h*k)
= (a*e - b*f - c*g - d*h)    +
  (a*f + b*e + c*h - d*g)*i  +
  (a*g - b*h + c*e + d*f)*j  +
  (a*h + b*g - c*f + d*e)*k

Visto dessa maneira, a multiplicação de quaternion pode ser vista como um mapa de um par de quatro tuplas para uma única quatro, que é o que você deve implementar.

Formato

Você deve escrever um programa ou uma função nomeada . Um programa deve receber entradas do STDIN e imprimir o resultado. Uma função deve receber entradas de função e retornar (não imprimir) uma saída.

Os formatos de entrada e saída são flexíveis. A entrada é oito números reais (os coeficientes para dois quaternions) e a saída consiste em quatro números reais. A entrada pode ter oito números, duas listas de quatro números, uma matriz 2x4 etc. O formato de entrada / saída não precisa ser o mesmo. A ordem dos (1,i,j,k)coeficientes depende de você.

Os coeficientes podem ser negativos ou não inteiros. Não se preocupe com precisão real ou transbordamentos.

Banido: Função ou tipos especificamente para quaterniões ou equivalentes.

Casos de teste

Estes estão no (1,i,j,k)formato coeficiente.

[[12, 54, -2, 23], [1, 4, 6, -2]] 
 [-146, -32, 270, 331]

[[1, 4, 6, -2], [12, 54, -2, 23]] 
 [-146, 236, -130, -333]

[[3.5, 4.6, -0.24, 0], [2.1, -3, -4.3, -12]] 
 [20.118, 2.04, 39.646, -62.5]

Implementação de referência

No Python, como função:

#Input quaternions: [a,b,c,d], [e,f,g,h]
#Coeff order: [1,i,j,k]

def mult(a,b,c,d,e,f,g,h):
    coeff_1 = a*e-b*f-c*g-d*h
    coeff_i = a*f+b*e+c*h-d*g
    coeff_j = a*g-b*h+c*e+d*f
    coeff_k = a*h+b*g-c*f+d*e

    result = [coeff_1, coeff_i, coeff_j, coeff_k]
    return result
xnor
fonte

Respostas:

4

CJam, 49 45 39 bytes

"cM-^\M-^G-^^KM-zP"256bGbq~m*f{=:*}4/{:-W*}/W*]`

O exemplo acima usa circunflexo e notação M, pois o código contém caracteres não imprimíveis.

Ao custo de dois bytes adicionais, esses caracteres podem ser evitados:

6Z9C8 7YDXE4BFA5U]q~m*f{=:*}4/{:-W*}/W*]`

Você pode experimentar esta versão online: Intérprete CJam

Casos de teste

Para calcular (a + bi + cj + dk) * (e + fi + gj + hk), use a seguinte entrada:

[ d c b a ] [ h g f e ]

A saída será

[ z y x w ]

que corresponde ao quaternion w + xi + yj + zk.

$ base64 -d > product.cjam <<< ImOchy0eS/pQIjI1NmJHYnF+bSpmez06Kn00L3s6LVcqfS9XKl1g
$ wc -c product.cjam
39 product.cjam
$ LANG=en_US cjam product.cjam <<< "[23 -2 54 12] [-2 6 4 1]"; echo
[331 270 -32 -146]
$ LANG=en_US cjam product.cjam <<< "[-2 6 4 1] [23 -2 54 12]"; echo
[-333 -130 236 -146]
$ LANG=en_US cjam product.cjam <<< "[0 -0.24 4.6 3.5] [-12 -4.3 -3 2.1]"; echo
[-62.5 39.646 2.04 20.118]

Como funciona

6Z9C8 7YDXE4BFA5U]  " Push the array [ 6 3 9 12 8 7 2 13 1 14 4 11 15 10 5 0].         ";
q~                  " Read from STDIN and interpret the input.                         ";
m*                  " Compute the cartesian product of the input arrays.               ";
f                   " Execute the following for each element of the first array:       ";
{                   " Push the cartesian product (implicit).                           ";
    =               " Retrieve the corresponding pair of coefficients.                 ";
    :*              " Calculate their product.                                         ";
}                   "                                                                  ";
4/                  " Split into chunks of 4 elements.                                 ";
{:-W*}/             " For each, subtract the first element from the sum of the others. ";
W*                  " Multiply the last integers (coefficient of 1) by -1.             ";
]`                  " Collect the results into an array and stringify it.              ";
Dennis
fonte
6

Python (83)

r=lambda A,B,R=range(4):[sum(A[m]*B[m^p]*(-1)**(14672>>p+4*m)for m in R)for p in R]

Pega duas listas A,Bem [1,i,j,k]ordem e retorna um resultado no mesmo formato.

A ideia principal é que, com os [1,i,j,k]correspondentes aos índices [0,1,2,3], você obtenha o índice do produto (até a assinatura) XOR'ing os índices. Portanto, os termos que são colocados no índice psão aqueles que indexam o XOR e p, portanto, são os produtos A[m]*B[m^p].

Resta apenas fazer os sinais funcionarem. A maneira mais curta que encontrei foi simplesmente codificá-los em uma sequência mágica. As 16 possibilidades de (m,p)são transformadas em números 0para 15as p+4*m. O número 14672no binário 1está nos locais onde os -1sinais são necessários. Ao deslocá-lo, o número apropriado de lugares, a 1ou 0termina no último dígito, tornando o número ímpar ou par, e assim (-1)**é 1ou -1conforme necessário.

xnor
fonte
A parte XOR é pura genialidade.
Dennis
3

Python - 90 75 72 69

Pure Python, sem bibliotecas - 90:

m=lambda a,b,c,d,e,f,g,h:[a*e-b*f-c*g-d*h,a*f+b*e+c*h-d*g,a*g-b*h+c*e+d*f,a*h+b*g-c*f+d*e]

Provavelmente é muito difícil diminuir essa solução "padrão" em Python. Mas estou muito curioso sobre o que os outros podem inventar. :)


Usando NumPy - 75 72 69:

Bem, como a entrada e a saída são bastante flexíveis, podemos usar algumas funções do NumPy e explorar a representação vetorial escalar :

import numpy
m=lambda s,p,t,q:[s*t-sum(p*q),s*q+t*p+numpy.cross(p,q)]

Argumentos de entrada se tsão as partes escalares dos dois quaterniões (as partes reais) e pe qsão as partes vetoriais correspondentes (as unidades imaginárias). Saída é uma lista que contém parte escalar e parte vetorial do quaternion resultante, sendo este último representado como matriz NumPy.

Script de teste simples:

for i in range(5):
    a,b,c,d,e,f,g,h=np.random.randn(8)
    s,p,t,q=a, np.array([b, c, d]), e, np.array([f, g, h])
    print mult(a, b, c, d, e, f, g, h), "\n", m(s,p,t,q)

( mult(...)sendo a implementação de referência do OP.)

Resultado:

[1.1564241702553644, 0.51859264077125156, 2.5839001110572792, 1.2010364098925583] 
[1.1564241702553644, array([ 0.51859264,  2.58390011,  1.20103641])]
[-1.8892934508324888, 1.5690229769129256, 3.5520713781125863, 1.455726589916204] 
[-1.889293450832489, array([ 1.56902298,  3.55207138,  1.45572659])]
[-0.72875976923685226, -0.69631848934167684, 0.77897519489219036, 1.4024428845608419] 
[-0.72875976923685226, array([-0.69631849,  0.77897519,  1.40244288])]
[-0.83690812141836401, -6.5476014589535243, 0.29693969165495304, 1.7810682337361325] 
[-0.8369081214183639, array([-6.54760146,  0.29693969,  1.78106823])]
[-1.1284033842268242, 1.4038096725834259, -0.12599103441714574, -0.5233468317643214] 
[-1.1284033842268244, array([ 1.40380967, -0.12599103, -0.52334683])]
Falko
fonte
2

Haskell, 85

m a b c d e f g h=[a*e-b*f-c*g-d*h,a*f+b*e+c*h-d*g,a*g-b*h+c*e+d*f,a*h+b*g-c*f+d*e]

Portá-lo para Haskell economiza alguns caracteres;)

ThreeFx
fonte
2

Mathematica 83 50

Provavelmente pode ser jogado mais ..

p = Permutations;
f = #1.(Join[{{1, 1, 1, 1}}, p[{-1, 1, -1, 1}][[1 ;; 3]]] p[#2][[{1, 8, 17, 24}]]) &

Espaços e novas linhas não são contados e não são necessários.

Uso:

f[{a,b,c,d},{e,f,g,h}]        (* => {x,w,y,z}   *)


EDITAR Como isso funciona.

A função Mathematica Permutationsfaz todas as permutações possíveis de #2(o segundo argumento). Há 24 permutações, mas precisamos apenas {e,f,g,h}, {f,e,h,g}, {g,h,e,f}, e {h,g,f,e}. Estas são as primeiras, 8ª, 17ª e 24ª permutações. Então o código

p[#2][[{1,8,17,24}]]

exatamente as seleciona das permutações do segundo argumento e as retorna como uma matriz. Mas eles ainda não têm o sinal correto. O código p[{-1,1,-1,1}][[1;;3]]retorna uma matriz 3x4 com o sinal correto. Nós o anexamos {1,1,1,1}usando Joine fazendo uma multiplicação normal ( Timesou, como é o caso aqui, apenas escrevendo-as uma após a outra) entre duas matrizes para fazer uma multiplicação elemento por elemento no Mathematica.

Então, finalmente, o resultado de

(Join[{{1, 1, 1, 1}}, p[{-1, 1, -1, 1}][[1 ;; 3]]] p[#2][[{1, 8, 17, 24}]])

é a matriz

 e  f  g  h
-f  e -h  g
-g  h  e -f
-h -g  f  e

Fazer uma multiplicação de matrizes entre {a,b,c,d}(o primeiro argumento #1) e a matriz anterior fornece o resultado desejado.



EDIT 2 Código mais curto

Inspirado no código Python de Falko, divido o quaternion em uma parte escalar e vetorial, e uso o comando integrado do Mathematica Crosspara calcular o produto cruzado das partes vetoriais:

f[a_, A_, b_, B_] := Join[{a*b - A.B}, a*B + b*A + Cross[A, B]]

Uso:

f[a,{b,c,d},e,{f,g,h}]        (* => {x,w,y,z}   *)
freddieknets
fonte
Você poderia explicar como isso funciona? O que são 1, 8, 17, 24?
Xnor
1

Python, 94

A maneira mais direta não é muito longa.

def m(a,b,c,d,e,f,g,h):return[a*e-b*f-c*g-d*h,a*f+b*e+c*h-d*g,a*g-b*h+c*e+d*f,a*h+b*g-c*f+d*e]
Florian F
fonte
1

JavaScript ES6 - 86

f=(a,b,c,d,e,f,g,h)=>[a*e-b*f-c*g-d*h,a*f+b*e+c*h-d*g,a*g-b*h+c*e+d*f,a*h+b*g-c*f+d*e]
William Barbosa
fonte
1

Lua - 99

Poderia muito bem.

_,a,b,c,d,e,f,g,h=unpack(arg)print(a*e-b*f-c*g-d*h,a*f+b*e+c*h-d*g,a*g-b*h+c*e+d*f,a*h+b*g-c*f+d*e)

"Unpack ()" de Lua libera os elementos de uma tabela. Portanto, a tabela 'arg' é onde todas as entradas da linha de comando são armazenadas (incluindo arg[0]qual é o nome do arquivo do programa, ela é descartada).

AndoDaan
fonte
1

Python, 58 56 caracteres

m=lambda x,y,z,w:(x*z-y*(2*w.real-w),x*w+y*(2*z.real-z))

Eu uso muito liberalmente a sala de manobra do formato de entrada / saída. As entradas são 4 números complexos, codificados da seguinte forma:

x = a+b*i
y = c+d*i
z = e+f*i
w = g+h*i

Ele gera um par de números complexos em um formato semelhante, o primeiro do par codifica o real e a iparte, o segundo codifica o je kpartes.

Para ver isso funciona, observe que o primeiro quaternion é x+y*je o segundo é z+w*j. Apenas avalie (x+y*j)*(z+w*j)e perceba que j*t= conj(t)*jpara qualquer número imaginário t.

Keith Randall
fonte
Muito esperto! Você sabe por que os quaternions parecem se multiplicar como números complexos com coeficientes complexos, como parece em sua expressão?
Xnor
Não se preocupe, agora entendo a partir de sua explicação como ie jagir como coeficientes complexos internos e externos. Que fascinante!
Xnor
É engraçado como as chamadas conjuntas ocupam mais de 2/5 dos seus caracteres. Eu acho que você pode raspar um char cada usando (2*w.real-w). abs(w)**2/wfuncionaria mas por 0. Talvez até o exec com substituição de string valha a pena? `
xnor
1

Sussurros v2 , 396 bytes

> 1
> 2
> 0
> 4
> Input
> Input
>> 6ᶠ2
>> 6ᵗ2
>> 7ⁿ3
>> 7ⁿ1
>> 10‖9
>> 8ⁿ3
>> 8ⁿ1
>> 13‖12
>> 7‖8
>> 11‖14
>> 8‖7
>> 14‖11
>> 15‖16
>> 19‖17
>> 20‖18
>> 4⋅5
>> L⋅R
>> Each 23 22 21
> [1,-1,-1,-1,1,1,1,-1,1,-1,1,1,1,1,-1,1]
>> Each 23 24 25
>> 26ᶠ4
>> 26ᵗ4
>> 28ᶠ4
> 8
>> 26ᵗ30
>> 31ᶠ4
>> 31ᵗ4
>> ∑27
>> ∑29
>> ∑32
>> ∑33
>> Output 34 35 36 37

Experimente online!

Recebe entrada no formulário

[a, b, c, d]
[e, f, g, h]

e saídas como

w
x
y
z

q=W+xEu+yj+zk

A árvore de estrutura desta resposta é:

árvore

Uma boa parte dessa resposta vem de duas falhas principais no Whispers:

  • Nenhuma função para reverter uma matriz
  • O uso de conjuntos no cálculo do produto cartesiano

Portanto, podemos dividir o código em 3 seções.

Como funciona

Usaremos as seguintes definições para maior clareza e concisão:

q=uma+bEu+cj+dk
p=e+fEu+gj+hk
r=W+xEu+yj+zk,(qp=r)
UMA=[uma,b,c,d]
B=[e,f,g,h]
C=[W,x,y,z]

UMAB

A primeira seção é de longe a mais longa, estendendo-se da linha 1 à linha 22 :

> 1
> 2
> 0
> 4
> Input
> Input
>> 6ᶠ2
>> 6ᵗ2
>> 7ⁿ3
>> 7ⁿ1
>> 10‖9
>> 8ⁿ3
>> 8ⁿ1
>> 13‖12
>> 7‖8
>> 11‖14
>> 8‖7
>> 14‖11
>> 15‖16
>> 19‖17
>> 20‖18
>> 4⋅5

BUMABBUMA

B1=[e,f,g,h]
B2=[f,e,h,g]
B3=[g,h,e,f]
B4=[h,g,f,e]

BBBB2B4

>> 7ⁿ3
>> 7ⁿ1
>> 10‖9

[f,e]

>> 8ⁿ3
>> 8ⁿ1
>> 13‖12

[h,g]B1,B2,B3B4BTUMATUMA4

UMAT=[uma,b,c,d,uma,b,c,d,uma,b,c,d,uma,b,c,d]
BT=[e,f,g,h,f,e,h,g,g,h,e,f,h,g,f,e]

BTUMATqp

Seção 2: Sinais e produtos

UMATBTqp

> [1,-1,-1,-1,1,1,1,-1,1,-1,1,1,1,1,-1,1]

SUMAT,BTS[[uma,e,1],[b,f,-1],...,[e,f,-1],[d,e,1]]D=[umae,-bf,...,-ef,de]

Seção 3: Partições e somas finais.

qpqp

> 4

54DD

caird coinheringaahing
fonte