Asignatura: Mecánica de Fluidos
Departamento: Ciencia y Tecnología de Materiales y Fluidos
Centro: Escuela Universitaria Politécnica de Teruel
Profesor: Adrián Navas Montilla
Las líneas características del flujo son líneas imaginarias trazadas en el campo fluido con unas propiedades particulares. Estas líneas se utilizan para visualizar y analizar el comportamiento del flujo. Podemos definir 3 tipos de líneas características:
Para ejemplificar los distintos tipos de líneas características del flujo, vamos a considerar el siguiente campo vectorial en 2 dimensiones, con dependencia espacial y temporal:
$$\vec{\bf{v}}(x,t)=\left(\begin{array}{c} 2 \\ 12t\cos(\pi x) + 0.5 \end{array} \right)$$Una línea de corriente es una línea imaginaria trazada en un campo fluido que es tangente en cada punto al vector de velocidad del flujo en ese punto. En otras palabras, en cada punto a lo largo de la línea de corriente, la dirección del flujo coincide con la dirección de la línea de corriente. Se utilizan para visualizar el patrón general del flujo y entender cómo se mueven las partículas en el campo de flujo. Se trata de un concepto instantáneo equivalente a tomar una foto en un instante determinado.
Denotemos $\vec{\bf{r}}=(x,y,z)$ al vector posición. Por definición:
$$\vec{\bf{v}}\times d\vec{\bf{r}}=0$$$$\frac{u}{dx}=\frac{v}{dy}=\frac{w}{dz}$$En la siguiente animación se muestran 3 líneas de corriente que pasan por los puntos $(0.1,0.4)$, $(0.2,0.4)$, $(0.3,0.4)$. Las líneas de corriente cambian en el tiempo ya que el flujo no es estacionario.
import math
import numpy as np # Librería para poder trabajar con matrices y vectores
import matplotlib.pyplot as plt # Librería para poder dibujar gráficas
from myst_nb import glue
from scipy.interpolate import Rbf
from matplotlib import animation
from IPython.display import HTML
nq=2 #separacion entre vectores en quiver plot
N=21
L=1.0
xp = np.linspace(0, L, N)
yp = np.linspace(0, L, N)
X, Y = np.meshgrid(xp, yp)
t=0.0
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title("Descripción Euleriana")
plt.tight_layout()
plt.close()
nframes=100 #frames de la animacion
tf=2*math.pi/20 #tiempo total
dt=tf/nframes #paso de tiempo
seed_points = np.array([[0.1, 0.2, 0.3], [0.4, 0.4, 0.4]])
def update_plot(num):
t = dt*num
dx=t*2.0
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) * 6*t + 0.5
ax.cla()
ax.quiver(X[::nq,::nq], Y[::nq,::nq], U[::nq,::nq], V[::nq,::nq])
ax.streamplot(X, Y, U, V, start_points=seed_points.T, color='tab:red', linewidth=2)
ax.plot(seed_points[0],seed_points[1],'go')
ax.set_xlabel("x")
ax.set_ylabel("y")
return
anim = animation.FuncAnimation(fig, update_plot, frames=nframes, interval=100, blit=False)
HTML(anim.to_html5_video())
La trayectoria contiene la información referente al camino o senda que recorre cada partícula fluida. Es el conjunto de puntos del espacio por los que ha pasado una particula fluida. Si la velocidad es constante en el tiempo la trayectoria coincide con la línea de corriente. Se trata de una historia temporal que muestra las posiciones que ha tenido una partícula fluida, por lo tanto no es un concepto instantáneo (el tiempo es la variable de integración).
Se calcula aplicando la definición de velocidad de una partícula fluida en un contexto lagrangiano:
$$\frac{d\vec{\bf{r}}}{dt}=\vec{\bf{v}}$$e integrando $d\vec{\bf{r}}=\vec{\bf{v}} dt$.
En la siguiente animación se muestran 3 trayectorias que pasan por los puntos $(0.1,0.4)$, $(0.2,0.4)$, $(0.3,0.4)$.
nq=2 #separacion entre vectores en quiver plot
N=21
L=1.0
xp = np.linspace(0, L, N)
yp = np.linspace(0, L, N)
X, Y = np.meshgrid(xp, yp)
t=0.0
fig, ax = plt.subplots(figsize=(7, 7))
plt.tight_layout()
plt.close()
nframes=100 #frames de la animacion
tf=2*math.pi/20 #tiempo total
dt=tf/nframes #paso de tiempo
XP = np.transpose(np.array([[0.1, 0.2, 0.3], [0.4, 0.4, 0.4]]))
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) * 6*t + 0.5
Q = ax.quiver(X[::nq,::nq], Y[::nq,::nq], U[::nq,::nq], V[::nq,::nq])
#ax.plot(seed_points[0],seed_points[1],'go')
P, = ax.plot(XP[:,0],XP[:,1], 'go', ms=10)
def update_plot(num):
t = dt*num
dx=t*2.0
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) * 6*t + 0.5
Q.set_UVC(U[::nq,::nq],V[::nq,::nq])
interpx = Rbf(X, Y, U)
interpy = Rbf(X, Y, V)
XP[:,0] = XP[:,0] + interpx(XP[:,0],XP[:,1])*dt
XP[:,1] = XP[:,1] + interpy(XP[:,0],XP[:,1])*dt
ax.plot(XP[:,0],XP[:,1], '.',color="tab:orange", ms=7)
P.set_data(XP[:,0],XP[:,1])
ax.set_xlabel("x")
ax.set_ylabel("y")
return
anim = animation.FuncAnimation(fig, update_plot, frames=nframes, interval=100, blit=False)
HTML(anim.to_html5_video())
La traza es el lugar geométrico de puntos del plano en el que se encuentran las partículas fluidas que han pasado por un mismo punto inicial. Imaginemos inyectar tinta o una serie de partículas en un fluido en movimiento. Estas partículas se moverán con el flujo y crearán una línea continua a medida que viajan. A esta línea se le llama traza.
En la figura inferior se muestra una ilustración de la traza formada por las partículas fluidas que han salido de una chimenea. En el tiempo $t_0$, la partícula fluida azul sale de la chimenea. En el tiempo $t_1$, la partícula fluida azul habrá sido arrastrada por el flujo siguiendo la dirección del viento, y una nueva partícula fluida (verde) saldrá de la chimenea. En el tiempo $t_2$, las partículas azul y verde han sido arrastradas por el flujo siguiendo la dirección del viento y una nueva partícula fluida (naranja) saldrá de la chimenea. Las posiciones previas de las partículas fluidas se indican con círculos huecos. La traza será la línea formada por las distintas partículas fluidas que han salido de la chimenea en cierto tiempo (por ejemplo en $t_3$). Si unimos las posiciones previas de una misma partícula fluida, obtendremos la trayectoria.
En la siguiente animación se muestran 3 trazas que se originan en los puntos $(0.1,0.4)$, $(0.2,0.4)$, $(0.3,0.4)$.
nq=2 #separacion entre vectores en quiver plot
N=21
L=1.0
xp = np.linspace(0, L, N)
yp = np.linspace(0, L, N)
X, Y = np.meshgrid(xp, yp)
t=0.0
fig, ax = plt.subplots(figsize=(7, 7))
plt.tight_layout()
plt.close()
nframes=100 #frames de la animacion
tf=2*math.pi/20 #tiempo total
dt=tf/nframes #paso de tiempo
XP = np.array([0.2,0.4],ndmin=2)
XP = np.repeat(XP, nframes, axis=0)
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) * 6*t + 0.5
Q = ax.quiver(X[::nq,::nq], Y[::nq,::nq], U[::nq,::nq], V[::nq,::nq])
#ax.plot(seed_points[0],seed_points[1],'go')
P, = ax.plot(XP[0,0],XP[0,1], 'go', ms=10)
Ptraza, = ax.plot(XP[:,0],XP[:,1], '.',color='tab:green', ms=7)
def update_plot(num):
t = dt*num
dx=t*2.0
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) * 6*t + 0.5
Q.set_UVC(U[::nq,::nq],V[::nq,::nq])
interpx = Rbf(X, Y, U)
interpy = Rbf(X, Y, V)
ii=num
XP[0:ii,0] = XP[0:ii,0] + interpx(XP[0:ii,0],XP[0:ii,1])*dt
XP[0:ii,1] = XP[0:ii,1] + interpy(XP[0:ii,0],XP[0:ii,1])*dt
Ptraza.set_data(XP[0:ii,0],XP[0:ii,1])
P.set_data(XP[0,0],XP[0,1])
ax.set_xlabel("x")
ax.set_ylabel("y")
return
anim = animation.FuncAnimation(fig, update_plot, frames=nframes, interval=100, blit=False)
HTML(anim.to_html5_video())
A continuación se muestra una comparación de trazas (verde) y trayectorias (naranja). La diferencia principal es que, en la traza (verde), los puntos que la componen son "arrastrados" por el flujo en el tiempo, al igual que ocurre en un soluto que se transporta en el agua. Por el contrario, los puntos que forman la trayectoria son fijos en el tiempo, ya que indican la posición de la partícula fluida en un tiempo dado.
nq=2 #separacion entre vectores en quiver plot
N=21
L=1.0
xp = np.linspace(0, L, N)
yp = np.linspace(0, L, N)
X, Y = np.meshgrid(xp, yp)
t=0.0
fig, ax = plt.subplots(figsize=(7, 7))
plt.tight_layout()
plt.close()
nframes=100 #frames de la animacion
tf=2*math.pi/20 #tiempo total
dt=tf/nframes #paso de tiempo
XP = np.array([0.2,0.4],ndmin=2)
XP = np.repeat(XP, nframes, axis=0)
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) * 6*t + 0.5
Q = ax.quiver(X[::nq,::nq], Y[::nq,::nq], U[::nq,::nq], V[::nq,::nq])
#ax.plot(seed_points[0],seed_points[1],'go')
P, = ax.plot(XP[0,0],XP[0,1], 'go', ms=10)
Ptraza, = ax.plot(XP[:,0],XP[:,1], '.',color='tab:green', ms=7)
seed_points = np.array([[0.2], [0.4]])
def update_plot(num):
t = dt*num
dx=t*2.0
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) * 6*t + 0.5
Q.set_UVC(U[::nq,::nq],V[::nq,::nq])
interpx = Rbf(X, Y, U)
interpy = Rbf(X, Y, V)
ii=num
XP[0:ii,0] = XP[0:ii,0] + interpx(XP[0:ii,0],XP[0:ii,1])*dt
XP[0:ii,1] = XP[0:ii,1] + interpy(XP[0:ii,0],XP[0:ii,1])*dt
#ax.streamplot(X, Y, U, V, start_points=seed_points.T, color='tab:red', linewidth=2)
ax.plot(XP[0,0],XP[0,1], '.',color="tab:orange", ms=7)
Ptraza.set_data(XP[0:ii,0],XP[0:ii,1])
P.set_data(XP[0,0],XP[0,1])
ax.set_xlabel("x")
ax.set_ylabel("y")
return
anim = animation.FuncAnimation(fig, update_plot, frames=nframes, interval=100, blit=False)
HTML(anim.to_html5_video())
Pero... ¿qué ocurrirá cuando tengamos un flujo estacionario?
A continuación se muestran la línea de corriente, traza y trayectoria para un flujo similar al anterior, pero sin dependencia temporal. Se observa que todas las líneas coinciden.
nq=2 #separacion entre vectores en quiver plot
N=21
L=1.0
xp = np.linspace(0, L, N)
yp = np.linspace(0, L, N)
X, Y = np.meshgrid(xp, yp)
t=0.0
fig, ax = plt.subplots(figsize=(7, 7))
plt.tight_layout()
plt.close()
nframes=100 #frames de la animacion
tf=2*math.pi/20 #tiempo total
dt=tf/nframes #paso de tiempo
XP = np.array([0.2,0.4],ndmin=2)
XP = np.repeat(XP, nframes, axis=0)
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) + 0.5
Q = ax.quiver(X[::nq,::nq], Y[::nq,::nq], U[::nq,::nq], V[::nq,::nq])
#ax.plot(seed_points[0],seed_points[1],'go')
P, = ax.plot(XP[0,0],XP[0,1], 'go', ms=10)
Ptraza, = ax.plot(XP[:,0],XP[:,1], '.',color='tab:green', ms=8)
seed_points = np.array([[0.2], [0.4]])
ax.streamplot(X, Y, U, V, start_points=seed_points.T, color='tab:red', linewidth=2)
def update_plot(num):
t = dt*num
dx=t*2.0
U = 2*np.ones(np.shape(X))
#V = 2*np.cos(np.pi * X + 0.25*np.sin(20*t)) * np.sin(np.pi * Y)
V = 2*np.cos(np.pi * X) + 0.5
Q.set_UVC(U[::nq,::nq],V[::nq,::nq])
interpx = Rbf(X, Y, U)
interpy = Rbf(X, Y, V)
ii=num
XP[0:ii,0] = XP[0:ii,0] + interpx(XP[0:ii,0],XP[0:ii,1])*dt
XP[0:ii,1] = XP[0:ii,1] + interpy(XP[0:ii,0],XP[0:ii,1])*dt
#ax.streamplot(X, Y, U, V, start_points=seed_points.T, color='tab:red', linewidth=2)
ax.plot(XP[0,0],XP[0,1], '.',color="tab:orange", ms=5)
Ptraza.set_data(XP[0:ii,0],XP[0:ii,1])
P.set_data(XP[0,0],XP[0,1])
ax.set_xlabel("x")
ax.set_ylabel("y")
return
anim2 = animation.FuncAnimation(fig, update_plot, frames=nframes, interval=100, blit=False)
HTML(anim2.to_html5_video())