2011-02-02 15 views
22

Tenemos un montón de coordenadas espaciales (x, y y z) que representan átomos en el espacio 3D, y estoy construyendo una función que traducirá estos puntos a un nuevo sistema de coordenadas. Cambiar las coordenadas a un origen arbitrario es simple, pero no puedo entender el próximo paso: cálculos de rotación de puntos en 3D. En otras palabras, trato de traducir los puntos de (x, y, z) a (x ', y', z '), donde x', y 'yz' están en términos de i ', j' y k ', los nuevos vectores de ejes que estoy haciendo con la ayuda de euclid python module.sistema de coordenadas giratorias a través de un cuaternión

Yo creo todo lo que necesito es un cuaternión Euclides para hacer esto, es decir

>>> q * Vector3(x, y, z) 
Vector3(x', y', z') 

pero para hacer que yo creo que necesito un vector eje de rotación y un ángulo de rotación. Pero no tengo idea de cómo calcular estos de i ', j' y k '. Esto parece un procedimiento simple para codificar desde cero, pero sospecho que algo como esto requiere que el álgebra lineal se resuelva por mi cuenta. Muchas gracias por un empujón en la dirección correcta.

+0

solo para aclarar, ¿quieres una transformación lineal de un 3-espacio euclidiano a otro 3-espacio euclidiano? – ThomasMcLeod

+3

Aquí hay una pista: ¿A qué se traducirían los vectores '(0, 0, 1)', '(0, 1, 0)' y '(1, 0, 0)'? –

+1

La matriz de rotación es la mejor opción aquí. La matriz de rotación que relaciona los cuadros de coordenadas es fácil de obtener y eficiente de aplicar. Obtener y aplicar un cuaternión aquí requeriría esencialmente convertir de la matriz de rotación y luego convertir nuevamente a la matriz de rotación. Obviamente, es mejor usar la matriz de rotación. Los cuaterniones tienen sus fortalezas en otros lugares. Sus puntos fuertes son que consisten en solo 4 números, pueden componerse y aplicarse sin trigonometría, tienen restricciones simples (magnitud de la unidad) y son muy adecuados para la interpolación. – Praxeolitic

Respuesta

53

El uso de cuaterniones para representar la rotación no es difícil desde un punto de vista algebraico. Personalmente, me resulta difícil razonar visualmente sobre cuaterniones, pero las fórmulas involucradas en su uso para las rotaciones son bastante simples. Proporcionaré un conjunto básico de funciones de referencia aquí. (Ver también esta respuesta preciosa por hosolmaz, en el que los paquetes de estos juntos para crear una clase Quaternion práctico.)

Se puede pensar en cuaterniones (para nuestros propósitos) como un escalar más un vector de 3-d - - abstractly, w + xi + yj + zk, aquí representado por una simple tupla (w, x, y, z). El espacio de rotaciones de 3-d está representado en su totalidad por un subespacio de los cuaterniones, el espacio de unidades de los cuaterniones de la unidad, por lo que debe asegurarse de que sus cuaterniones estén normalizados. Puede hacerlo de la manera que normalizaría cualquier 4-vector (es decir, la magnitud debe ser cercano a 1, y si no es así, reducir la escala de los valores de la magnitud):

def normalize(v, tolerance=0.00001): 
    mag2 = sum(n * n for n in v) 
    if abs(mag2 - 1.0) > tolerance: 
     mag = sqrt(mag2) 
     v = tuple(n/mag for n in v) 
    return v 

Tenga en cuenta que para simplicidad, las siguientes funciones asumen que los valores de quaternion son ya normalizados. En la práctica, tendrá que renormalizarlos de vez en cuando, pero la mejor manera de lidiar con eso dependerá del dominio del problema. Estas funciones proporcionan solo lo básico, solo como referencia.

Cada rotación está representada por una unidad quaternion, y las concatenaciones de rotaciones corresponden a multiplicaciones de unidades quaternions. La fórmula de esto es la siguiente:

def q_mult(q1, q2): 
    w1, x1, y1, z1 = q1 
    w2, x2, y2, z2 = q2 
    w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 
    x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 
    y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2 
    z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 
    return w, x, y, z 

Para rotar un vectorpor un cuaternión, necesita conjugado del cuaternión también.Eso es fácil:

def q_conjugate(q): 
    w, x, y, z = q 
    return (w, -x, -y, -z) 

Ahora cuaternión-vector de multiplicación es tan simple como convertir un vector en un cuaternión (mediante el establecimiento de w = 0 y dejando x, y y z la misma) y luego multiplicando q * v * q_conjugate(q):

def qv_mult(q1, v1): 
    q2 = (0.0,) + v1 
    return q_mult(q_mult(q1, q2), q_conjugate(q1))[1:] 

Finalmente, debe saber cómo convertir las rotaciones de eje-ángulo en cuaterniones. ¡También es fácil! Tiene sentido "desinfectar" la entrada y la salida aquí llamando al normalize.

def axisangle_to_q(v, theta): 
    v = normalize(v) 
    x, y, z = v 
    theta /= 2 
    w = cos(theta) 
    x = x * sin(theta) 
    y = y * sin(theta) 
    z = z * sin(theta) 
    return w, x, y, z 

y Fondo:

def q_to_axisangle(q): 
    w, v = q[0], q[1:] 
    theta = acos(w) * 2.0 
    return normalize(v), theta 

Aquí está un ejemplo de uso rápido. Una secuencia de rotaciones de 90 grados sobre los ejes x, y y z devolverá un vector en el eje y a su posición original. Este código realiza esas rotaciones:

x_axis_unit = (1, 0, 0) 
y_axis_unit = (0, 1, 0) 
z_axis_unit = (0, 0, 1) 
r1 = axisangle_to_q(x_axis_unit, numpy.pi/2) 
r2 = axisangle_to_q(y_axis_unit, numpy.pi/2) 
r3 = axisangle_to_q(z_axis_unit, numpy.pi/2) 

v = qv_mult(r1, y_axis_unit) 
v = qv_mult(r2, v) 
v = qv_mult(r3, v) 

print v 
# output: (0.0, 1.0, 2.220446049250313e-16) 

tener en cuenta que esta secuencia de rotaciones no volverá todos vectores a la misma posición; por ejemplo, para un vector en el eje x, corresponderá a una rotación de 90 grados alrededor del eje y. (Mantenga la derecha en reglas en mente aquí, una rotación positiva sobre el eje y empuja un vector en el eje x en la región z negativa .)

v = qv_mult(r1, x_axis_unit) 
v = qv_mult(r2, v) 
v = qv_mult(r3, v) 

print v 
# output: (4.930380657631324e-32, 2.220446049250313e-16, -1.0) 

Como siempre, por favor, hágamelo saber si usted encuentra algún problema aquí.


1. Estos están adaptados a partir de un tutorial OpenGL archived here.

2. La fórmula de multiplicación del cuaternión parece un loco nido de ratas, pero la derivación es simple (si es tediosa). Solo tenga en cuenta primero que ii = jj = kk = -1; luego que ij = k, jk = i, ki = j; y finalmente que ji = -k, kj = -i, ik = -j. Luego, multiplica los dos cuaterniones, distribuyendo los términos y reorganizándolos en función de los resultados de cada una de las 16 multiplicaciones. Esto también ayuda a ilustrar por qué puede usar cuaterniones para representar la rotación; las últimas seis identidades siguen la regla de la mano derecha, creando biyecciones entre rotaciones dei a j y rotaciones alrededor dek, y así sucesivamente.

+1

Espero que no te importe, he corregido un par de errores tipográficos (ver ediciones). Además, para que 'qv_mult' sea útil, debería devolver un vector, no un cuaternión, así que he descartado el primer componente (¡que de todos modos es cero!). – Hooked

+0

@Hooked, de acuerdo, gracias! – senderle

+0

Creo que la primera línea de 'qv_mult' debería ser' q1 = normalize (q1) '(no' v1')? –

2

Tenga en cuenta que la inversión de la matriz no es tan trivial en absoluto! En primer lugar, todos los puntos n (donde n es la dimensión de su espacio) deben estar en posición general (es decir, ningún punto individual puede expresarse como una combinación lineal del resto de los puntos [advertencia: esto puede parecer un simple requisito, pero en el ámbito del álgebra lineal numérica, no es trivial; la decisión final, si dicha configuración existe o no, con el tiempo se basará en el conocimiento específico del "dominio real"]).

También es posible que la "correspondencia" de los puntos nuevos y viejos no sea exacta (y luego debe utilizar el mejor aproximador posible de la "correspondencia verdadera", es decir :).Pseudo inverso (en lugar de tratar de utilizar el inverso simple) se recomienda siempre cuando tu lib lo proporcione.

La pseudo inversa tiene la ventaja de que podrá usar más puntos para su transformación, lo que aumenta la probabilidad de que al menos n puntos estén en posición general.

Aquí hay un ejemplo, rotación de la unidad cuadrada de 90 grados. CCW en 2D (pero obviamente esta determinación funciona en cualquier tenue), con numpy:

In []: P= matrix([[0, 0, 1, 1], 
        [0, 1, 1, 0]]) 
In []: Pn= matrix([[0, -1, -1, 0], 
        [0, 0, 1, 1]]) 
In []: T= Pn* pinv(P) 
In []: (T* P).round() 
Out[]: 
matrix([[ 0., -1., -1., 0.], 
     [ 0., 0., 1., 1.]]) 

P. S. numpy también es rápido. Transformación de 1 millón de puntos en mi equipo modesto:

In []: P= matrix(rand(2, 1e6)) 
In []: %timeit T* P 
10 loops, best of 3: 37.7 ms per loop 
+0

gracias por esto, numpy parece una alternativa muy atractiva a euclid. mi obstáculo principal fue simplemente resolver las matemáticas sin antecedentes en álgebra lineal. [xyz] * [i'j'k ']^- 1 = [x'y'z'] era la parte que faltaba. –

2

Esta pregunta y la respuesta dada por @senderle realmente me ayudaron con uno de mis proyectos. La respuesta es mínima y cubre el núcleo de la mayoría de los cálculos de cuaternión que uno podría necesitar realizar.

Para mi propio proyecto, me pareció tedioso tener funciones separadas para todas las operaciones e importarlas una por una cada vez que las necesito, así que implementé una versión orientada a objetos.

quaternion.py:

import numpy as np 
from math import sin, cos, acos, sqrt 

def normalize(v, tolerance=0.00001): 
    mag2 = sum(n * n for n in v) 
    if abs(mag2 - 1.0) > tolerance: 
     mag = sqrt(mag2) 
     v = tuple(n/mag for n in v) 
    return np.array(v) 

class Quaternion: 

    def from_axisangle(theta, v): 
     theta = theta 
     v = normalize(v) 

     new_quaternion = Quaternion() 
     new_quaternion._axisangle_to_q(theta, v) 
     return new_quaternion 

    def from_value(value): 
     new_quaternion = Quaternion() 
     new_quaternion._val = value 
     return new_quaternion 

    def _axisangle_to_q(self, theta, v): 
     x = v[0] 
     y = v[1] 
     z = v[2] 

     w = cos(theta/2.) 
     x = x * sin(theta/2.) 
     y = y * sin(theta/2.) 
     z = z * sin(theta/2.) 

     self._val = np.array([w, x, y, z]) 

    def __mul__(self, b): 

     if isinstance(b, Quaternion): 
      return self._multiply_with_quaternion(b) 
     elif isinstance(b, (list, tuple, np.ndarray)): 
      if len(b) != 3: 
       raise Exception(f"Input vector has invalid length {len(b)}") 
      return self._multiply_with_vector(b) 
     else: 
      raise Exception(f"Multiplication with unknown type {type(b)}") 

    def _multiply_with_quaternion(self, q2): 
     w1, x1, y1, z1 = self._val 
     w2, x2, y2, z2 = q2._val 
     w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2 
     x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 
     y = w1 * y2 + y1 * w2 + z1 * x2 - x1 * z2 
     z = w1 * z2 + z1 * w2 + x1 * y2 - y1 * x2 

     result = Quaternion.from_value(np.array((w, x, y, z))) 
     return result 

    def _multiply_with_vector(self, v): 
     q2 = Quaternion.from_value(np.append((0.0), v)) 
     return (self * q2 * self.get_conjugate())._val[1:] 

    def get_conjugate(self): 
     w, x, y, z = self._val 
     result = Quaternion.from_value(np.array((w, -x, -y, -z))) 
     return result 

    def __repr__(self): 
     theta, v = self.get_axisangle() 
     return f"((%.6f; %.6f, %.6f, %.6f))"%(theta, v[0], v[1], v[2]) 

    def get_axisangle(self): 
     w, v = self._val[0], self._val[1:] 
     theta = acos(w) * 2.0 

     return theta, normalize(v) 

    def tolist(self): 
     return self._val.tolist() 

    def vector_norm(self): 
     w, v = self.get_axisangle() 
     return np.linalg.norm(v) 

En esta versión, uno puede utilizar los operadores sobrecargados para cuaterniones cuaternión y la multiplicación de cuaterniones en vectores

from quaternion import Quaternion 
import numpy as np 

x_axis_unit = (1, 0, 0) 
y_axis_unit = (0, 1, 0) 
z_axis_unit = (0, 0, 1) 

r1 = Quaternion.from_axisangle(np.pi/2, x_axis_unit) 
r2 = Quaternion.from_axisangle(np.pi/2, y_axis_unit) 
r3 = Quaternion.from_axisangle(np.pi/2, z_axis_unit) 

# Quaternion - vector multiplication 
v = r1 * y_axis_unit 
v = r2 * v 
v = r3 * v 

print(v) 

# Quaternion - quaternion multiplication 
r_total = r3 * r2 * r1 
v = r_total * y_axis_unit 

print(v) 

No tenía la intención de implementar una completa -cuatro módulo cuaternario, así que esto es nuevamente para propósitos de instrucción, como en la gran respuesta de @ senderle. Espero que esto ayude a aquellos que quieran comprender y probar cosas nuevas con cuaterniones.

Cuestiones relacionadas