Si SymPy te ha parecido hasta ahora un CAS decente e incluso interesante (nada como tener los resultados en $\LaTeX$ incrustados en el notebook y la sintaxis de Python para hacer cálculo simbólico) entonces espera a ver el paquete mechanics
. Con él, podremos manipular velocidades y aceleraciones de sólidos expresadas en distintos sistemas de referencia con una facilidad impresionante.
Tienes disponible la documentación de mechanics
en http://docs.sympy.org/0.7.5/modules/physics/mechanics/index.html.
El objeto primordial que vamos a manejar van a ser los sistemas de referencia. Podremos definir relaciones geométricas entre ellos y de esta forma las transformaciones de vectores entre un sistema y otro serán triviales.
from sympy import init_session
init_session(use_latex=True)
IPython console for SymPy 1.0 (Python 3.6.1-64-bit) (ground types: python) These commands were executed: >>> from __future__ import division >>> from sympy import * >>> x, y, z, t = symbols('x y z t') >>> k, m, n = symbols('k m n', integer=True) >>> f, g, h = symbols('f g h', cls=Function) >>> init_printing() Documentation can be found at http://docs.sympy.org/1.0/
Todo lo que necesitamos está en sympy.physics.mechanics
, incluyendo la clase ReferenceFrame
. Nada más crear un sistema de referencia podemos acceder a sus versores unitarios: x
, y
y z
.
http://docs.sympy.org/0.7.5/modules/physics/vector/vectors.html
from sympy.physics.mechanics import ReferenceFrame
A = ReferenceFrame("A")
A.x
Y para definir vectores solo tenemos que multiplicar cada componente por su versor:
2 * A.x - 1 * A.y
De ahora en adelante, para trabajar como si nos enfrentáramos a un problema de la escuela, vamos a hacer dos cosas:
A = ReferenceFrame("1", latexs=['\mathbf{i}', '\mathbf{j}', '\mathbf{k}'])
A.x + A.y + A.z
Y para no tener que hacerlo siempre, un pequeño truco de magia:
# Definimos nuestra propia clase para que los versores sean IJK
class IJKReferenceFrame(ReferenceFrame):
def __init__(self, name):
super().__init__(name, latexs=['\mathbf{%s}_{%s}' % (idx, name) for idx in ("i", "j", "k")])
self.i = self.x
self.j = self.y
self.k = self.z
A = IJKReferenceFrame("1")
A.i + A.j + A.k
Nuestros vectores funcionan también con símbolos, y podemos realizar las operaciones de producto escalar y producto vectorial con ellos.
R, V = symbols('R, V', positive=True)
r1 = R * (A.x + A.y + A.z)
v1 = V * (A.x - 2 * A.z)
r1
v1
from sympy.physics.mechanics import dot, cross
r1.dot(v1)
dot(r1, v1)
r1 & v1
r1.cross(v1)
cross(r1, v1)
r1 ^ v1
Podemos hallar también la norma de los vectores con su método magnitude
e incluso normalizarlos con normalize
:
(r1 ^ v1).magnitude()
(r1 ^ v1).normalize()
Usando directamente la fórmula para la derivada en ejes móviles:
$$\left(\frac{\operatorname{d}\!\mathbf{a}}{\operatorname{d}\!t}\right)_1 = \left(\frac{\operatorname{d}\!\mathbf{a}}{\operatorname{d}\!t}\right)_0 + \mathbf{\omega}_{01}\! \times \mathbf{a}$$Calcula la derivada del vector de posición $R \mathbf{i}_0$, siendo $A_0$ un sistema de referencia que gira respecto al inercial con velocidad angular $\mathbf{\omega}_{01}=\Omega \mathbf{k}_0$. ¿Cuál es el módulo de la derivada?
R, Omega = symbols('R, Omega', positive=True)
A0 = IJKReferenceFrame('0')
a = R * A0.i
a
omega01 = Omega * A0.k
omega01
da = omega01 ^ a
da
da.magnitude()
¿A quién no le gusta multiplicar matrices de rotación? Para esa minoría que lo detesta, existe SymPy. Para ello debemos especificar la orientación de nuestros sistemas de referencia usando el método orient
, y recuperaremos la matriz de cosenos directores usando el método dcm
.
A1 = IJKReferenceFrame("1")
A0 = IJKReferenceFrame("0")
phi = symbols('phi')
A0.orient(A1, 'Axis', [phi, A1.z]) # Rotación phi alrededor del eje A1.z
A0.dcm(A1) # "Direct Cosine Matrix"
Usando el argumento Axis
hemos especificado que rotamos el sistema un ángulo especificado alrededor de un eje. Otros métodos son:
Body
: se especifican los tres ángulos de Euler.Space
: igual que Body
, pero las rotaciones se aplican en orden inverso.Quaternion
: utilizando cuaternios, rotación alrededor de un vector unitario $\lambda$ una cantidad $\theta$.Para expresar un vector en otro sistema de referencia, no hay más que usar los métodos express
o to_matrix
:
A0.x.express(A1)
A0.x.to_matrix(A1)
Si queremos especificar que un símbolo puede variar con el tiempo, hay que usar la función dynamicsymbols
:
from sympy.physics.mechanics import dynamicsymbols
alpha = dynamicsymbols('alpha')
alpha
Y pedir su derivada con el método diff
:
alpha.diff()
(Sacado de Cuerva et al. "Teoría de los Helicópteros")
Obtener la matriz de rotación de la pala $B$ respecto a los ejes $A1$.
A = IJKReferenceFrame("A")
A1 = IJKReferenceFrame("A1")
psi = dynamicsymbols('psi')
A1.orient(A, 'Axis', [psi, A.z])
A1.dcm(A) # T_{A1A}
A2 = IJKReferenceFrame("A2")
beta = dynamicsymbols('beta')
A2.orient(A1, 'Axis', [beta, -A1.y])
A2.dcm(A1) # T_{A2A1}
A3 = IJKReferenceFrame("A3")
zeta = dynamicsymbols('zeta')
A3.orient(A2, 'Axis', [zeta, A2.z])
A3.dcm(A1) # T_{A3A1}
B = IJKReferenceFrame("B")
theta = dynamicsymbols('theta')
B.orient(A3, 'Axis', [theta, A3.x])
B.dcm(A3) # T_{BA3}
B.dcm(A2)
B.dcm(A1)
También podemos hallar la velocidad angular de un sistema respecto a otro usando el método ang_vel_in
:
B.ang_vel_in(A2)
B.ang_vel_in(A)
B.ang_vel_in(A).express(A)
Hacer una derivada con la fórmula lo hace cualquiera, pero SymPy puede encargarse automáticamente.
v1 = A1.x
v1
#v1.diff(dynamicsymbols._t, A2)
dv1 = v1.diff(symbols('t'), A)
dv1
dv1.to_matrix(A1)
(dv1 & A1.j).simplify()
El último paso que nos queda para completar la cinemática es la posibilidad de definir puntos en sólidos y aplicar su campo de velocidades. SymPy también permite esto, y para ello no tenemos más que importar la clase Point
.
from sympy.physics.mechanics import Point
O = Point("O")
Para trabajar como lo haríamos en la escuela, vamos a especificar que $O$ es el origen de $A$, y para eso vamos a imponer que su velocidad es cero con el método set_vel
:
O.set_vel(A, 0)
Para definir nuevos puntos, podemos utilizar el método locate_new
:
e_b = symbols('e_b')
E_b = O.locatenew('E_b', e_b * A1.x)
Y para obtener vectores de un punto a otro, el método pos_from
:
E_b.pos_from(O)
Por último, el campo de velocidades de un sólido rígido se formula usando el método v2pt_theory
.
Este método pertenece al punto del cual queremos conocer la velocidad y recibe tres parámetros:
O
, punto de velocidad conocida respecto a AA
, sistema de referencia donde queremos calcular la velocidadA1
, sistema de referencia donde están fijos ambos puntos (sistema de arrastre)Por tanto, para hallar la velocidad del punto que acabamos de crear:
E_b.v2pt_theory(O, A, A1)
(Apuntes del profesor Dr. Óscar López Rebollal)
¡Halla la velocidad y la aceleración de $P$!
# Creamos nuestros sistemas de referencia
A1 = IJKReferenceFrame('1')
A0 = IJKReferenceFrame('0')
A2 = IJKReferenceFrame('2')
# Creamos los símbolos dinámicos necesarios
xi, theta = dynamicsymbols('xi, theta')
xi, theta
# Orientamos los sistemas de referencia
A0.orient(A1, 'Axis', [0, A1.k]) # A0 no gira respecto a A1
A2.orient(A0, 'Axis', [theta, A0.k])
A2.dcm(A1)
# Creamos el punto C, centro del disco, y especificamos su velocidad
# respecto a A1
C = Point('C')
C.set_vel(A1, xi.diff() * A1.x)
# Localizamos el punto P, punto fijo del disco, respecto a C, en
# el sistema A2 (que gira solidariamente con el disco)
R = symbols('R')
P = C.locatenew('P', -R * A2.j)
P.pos_from(C)
# Hallamos la velocidad de P en A1, expresada en A0
# ¡Con esta llamada ya estamos diciendo que C y P son fijos en A2!
P.v2pt_theory(C, A1, A2).express(A0)
Misión cumplida :)
Hemos hecho un repaso bastante profundo de las posibilidades del paquete mechanics
de SymPy. Nos hemos dejado algunas cosas en el tintero pero no demasiadas: esta funcionalidad aún se está expandiendo y necesita pulir algunos detalles.
Referencias
¿Serás tú el siguiente que publique un notebook usando SymPy? ;)
Las siguientes celdas contienen configuración del Notebook
Para visualizar y utlizar los enlaces a Twitter el notebook debe ejecutarse como seguro
File > Trusted Notebook
%%html
<a href="https://twitter.com/Pybonacci" class="twitter-follow-button" data-show-count="false">Follow @Pybonacci</a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
# Esta celda da el estilo al notebook
from IPython.core.display import HTML
css_file = '../styles/aeropython.css'
HTML(open(css_file, "r").read())