Multiplicar Cuaterniones

13

Escriba una función o programa con nombre que calcule el producto quaternion de dos cuaterniones. Use la menor cantidad de bytes posible.

Cuaterniones

Los cuaterniones son una extensión de los números reales que amplía aún más los números complejos. En lugar de una sola unidad imaginaria i, los cuaterniones usan tres unidades imaginarias i,j,kque satisfacen las relaciones.

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

(También hay tablas de estos en la página de Wikipedia ).

En palabras, cada unidad imaginaria se cuadra con -1, y el producto de dos unidades imaginarias diferentes es la tercera restante, +/-dependiendo de si (i,j,k)se respeta el orden cíclico (es decir, la regla de la mano derecha ). Entonces, el orden de multiplicación es importante.

Un cuaternión general es una combinación lineal de una parte real y las tres unidades imaginarias. Entonces, se describe con cuatro números reales (a,b,c,d).

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

Entonces, podemos multiplicar dos cuaterniones usando la propiedad distributiva, teniendo cuidado de multiplicar las unidades en el orden correcto y agrupando términos similares en el 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 de esta manera, la multiplicación de cuaterniones puede verse como un mapa de un par de 4 tuplas a un solo 4 tuplas, que es lo que se le pide que implemente.

Formato

Debe escribir un programa o una función con nombre . Un programa debe tomar entradas de STDIN e imprimir el resultado. Una función debe tomar entradas de función y devolver (no imprimir) una salida.

Los formatos de entrada y salida son flexibles. La entrada es ocho números reales (los coeficientes para dos cuaterniones), y la salida consta de cuatro números reales. La entrada puede ser ocho números, dos listas de cuatro números, una matriz de 2x4, etc. El formato de entrada / salida no tiene que ser el mismo. El orden de los (1,i,j,k)coeficientes depende de usted.

Los coeficientes pueden ser negativos o no completos. No se preocupe por la precisión real o los desbordamientos.

Prohibido: función o tipos específicos para cuaterniones o equivalentes.

Casos de prueba

Estos están en (1,i,j,k)formato de 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]

Implementación de referencia

En Python, como función:

#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
fuente

Respuestas:

4

CJam, 49 45 39 bytes

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

Lo anterior usa notación caret y M, ya que el código contiene caracteres no imprimibles.

A costa de dos bytes adicionales, esos caracteres se pueden evitar:

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

Puede probar esta versión en línea: intérprete de CJam

Casos de prueba

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

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

La salida será

[ z y x w ]

que corresponde al cuaternión 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]

Cómo 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
fuente
6

Pitón (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]

Toma dos listas A,Ben [1,i,j,k]orden y devuelve un resultado en el mismo formato.

La idea clave es que con los [1,i,j,k]índices correspondientes [0,1,2,3], obtienes el índice del producto (hasta el signo) haciendo XOR de los índices. Entonces, los términos que se colocan en el índice pson aquellos que indican XOR y p, por lo tanto, son los productos A[m]*B[m^p].

Solo queda hacer que los signos funcionen. La forma más corta que encontré fue simplemente codificarlos en una cadena mágica. Los 16 posibilidades (m,p)se convierten en números 0a 15como p+4*m. El número 14672en binario tiene 1's en los lugares donde -1se necesitan signos. Al desplazarlo a la cantidad adecuada de lugares, a 1o 0termina en el último dígito, haciendo que el número sea par o impar, y así (-1)**es 1o -1según sea necesario.

xnor
fuente
La parte XOR es puro genio.
Dennis
3

Python - 90 75 72 69

Python puro, sin 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]

Probablemente sea bastante difícil acortar esta solución "predeterminada" en Python. Pero tengo mucha curiosidad en cuanto a qué otros podrían llegar. :)


Usando NumPy - 75 72 69:

Bueno, dado que la entrada y la salida son bastante flexibles, podemos usar algunas funciones NumPy y explotar la representación del vector 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 sy tson las partes escalar de los dos cuaterniones (las partes reales) y py qson las partes vector correspondiente (las unidades imaginarias). La salida es una lista que contiene la parte escalar y la parte vectorial del cuaternión resultante, este último representado como una matriz NumPy.

Script de prueba simple:

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(...)siendo la implementación de referencia del OP).

Salida:

[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
fuente
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]

Portarlo a Haskell nos ahorra algunos caracteres;)

ThreeFx
fuente
2

Mathematica 83 50

Probablemente se pueda jugar más al golf.

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

Espacios y líneas nuevas no contados y no necesarios.

Uso:

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


EDITAR Cómo funciona esto.

La función Mathematica Permutationshace todas las permutaciones posibles de #2(el segundo argumento). Hay 24 permutaciones, pero sólo se necesitan {e,f,g,h}, {f,e,h,g}, {g,h,e,f}, y {h,g,f,e}. Estas son las permutaciones primera, 8ª, 17ª y 24ª. Entonces el código

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

los selecciona exactamente de las permutaciones del segundo argumento y los devuelve como una matriz. Pero aún no tienen la señal correcta. El código p[{-1,1,-1,1}][[1;;3]]devuelve una matriz 3x4 con el signo correcto. Lo anteponemos {1,1,1,1}usando Join, y haciendo una multiplicación normal ( Timeso, como es el caso aquí, simplemente escribiéndolos uno después del otro) entre dos matrices hace una multiplicación elemento por elemento en Mathematica.

Finalmente, el resultado de

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

es la matriz

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

Hacer una matriz de multiplicación entre {a,b,c,d}(el primer argumento #1) y la matriz anterior da el resultado deseado.



EDITAR 2 Código más corto

Inspirado por el código Python de Falko, separé el cuaternión en una parte escalar y una parte vectorial, y uso el comando incorporado de Mathematica Crosspara calcular el producto cruzado de las partes vectoriales:

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
fuente
¿Podría explicar cómo funciona esto? ¿Cuáles son 1, 8, 17, 24?
xnor
1

Python, 94

La forma más directa no es demasiado larga.

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
fuente
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
fuente
1

Lua - 99

Podría también.

_,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)

El "unpack ()" de Lua libera los elementos de una tabla. Entonces, la tabla 'arg' es donde se almacena toda la entrada de la línea de comandos (incluido arg[0]cuál es el nombre del archivo del programa, se descarta).

AndoDaan
fuente
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))

Aprovecho muy liberalmente el margen de maniobra del formato de entrada / salida. Las entradas son 4 números complejos, codificados así:

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

Produce un par de números complejos en un formato similar, el primero del par codifica el real y la iparte, el segundo codifica el jyk partes .

Para ver que esto funciona, tenga en cuenta que el primer cuaternión es x+y*jy el segundo es z+w*j. Solo evalúa (x+y*j)*(z+w*j)y date cuenta de que j*t= conj(t)*jpara cualquier número imaginario t.

Keith Randall
fuente
¡Muy inteligente! ¿Sabes por qué los cuaterniones parecen multiplicarse como números complejos con coeficientes complejos, como se ve por tu expresión?
xnor
No importa, ahora entiendo por su explicación cómo iy jactúan como coeficientes complejos internos y externos. ¡Que fascinante!
xnor
Es curioso cómo las llamadas conjuntas ocupan más de 2/5 de tus caracteres. Creo que puedes afeitarte un carbón usando cada uno (2*w.real-w). abs(w)**2/wfuncionaría pero para 0. ¿Quizás valdría la pena incluso un ejecutivo con sustitución de cadena? `
xnor
1

Whispers 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

Pruébalo en línea!

Toma entrada en el formulario

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

y salidas como

w
x
y
z

representar q=w+Xyo+yj+zk

El árbol de estructura de esta respuesta es:

tree

Una buena parte de esta respuesta proviene de dos fallas principales en Whispers:

  • Sin función para revertir una matriz
  • El uso de conjuntos en el cálculo del producto cartesiano

Por lo tanto, podemos dividir el código en 3 secciones.

Cómo funciona

Usaremos las siguientes definiciones para claridad y concisión:

q=un+siyo+Cj+rek
pag=mi+Fyo+solj+hk
r=w+Xyo+yj+zk,(qpag=r)
UN=[un,si,C,re]
si=[mi,F,sol,h]
C=[w,X,y,z]

Sección 1: Permutando UN y si

La primera sección es, con mucho, la más larga, desde la línea 1 hasta la línea 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

El objetivo principal de esta sección es permutar si entonces esa simple multiplicación entre elementos UN y sies posible. Hay cuatro arreglos diferentes desi para multiplicar los elementos de UN con:

si1=[mi,F,sol,h]
si2=[F,mi,h,sol]
si3=[sol,h,mi,F]
si4 4=[h,sol,F,mi]

La segunda entrada, si, se almacena en la línea 6 . Luego nos separamossi en el medio, ya que cada posible arreglo de sise agrupa en pares. Para revertir estos pares (para obtener los pedidos correctos ensi2 y si4 4), tomamos el primer y último elemento, luego los concatenamos en orden inverso:

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

(formando [F,mi]) y

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

(formando [h,sol]) Ahora tenemos todas las mitades necesarias para formar los arreglos, por lo que los concatenamos juntos para formarsi1,si2,si3 y si4 4. Finalmente, concatenamos estos cuatro arreglos para formarsiT. Luego hacemos rápidamenteUNT, definido como UN repetido 4 4 veces:

UNT=[un,si,C,re,un,si,C,re,un,si,C,re,un,si,C,re]
siT=[mi,F,sol,h,F,mi,h,sol,sol,h,mi,F,h,sol,F,mi]

Cuando cada elemento de siT se multiplica por el elemento correspondiente en UNT, obtenemos los valores (sin signo) en qpag

Sección 2: Señales y productos

Como se dijo en la Sección 1, los valores en UNT y siT corresponden a los valores sin signo (es decir, positivos) de cada uno de los coeficientes en qpag. No se encuentra ningún patrón obvio en los signos que serían más cortos que simplemente codificar la matriz, por lo que codificamos la matriz:

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

Llamaremos a esta matriz S (signs). Next, we zip together each element in AT,BT and S and take the product of each sub-array e.g. [[a,e,1],[b,f,1],,[e,f,1],[d,e,1]]D=[ae,bf,,ef,de].

Section 3: Partitions and final sums.

Once we have the array of coefficients of qp, with signs, we need to split it into 4 parts (i.e. the four factorised coefficients of qp), and then take the sums. This leads us to the only golfing opportunity found: moving the

> 4

to line 4 rather than 26, as it is used 6 times, each time saving a byte by moving it. Unfortunately, this costs a byte changing the 9 to a 10, so only 5 bytes are saved. The next section takes slices of size 4 from the front of D, saving each slice to the corresponding row and passing on the shortened list of D. Once 4 slices are taken, we the take the sum of each, before outputting them all.

caird coinheringaahing
fuente