sandbox/easystab/easyPYstab/RayleighBenard.py
Rayleigh-Benard instability
This document belongs to the easystab project, please consult the main page of the project for explanations.
The present program was specifically designed as a support for the course “Introductions to hydrodynamical instabilities” for the “DET” Master’s cursus in Toulouse. For the underlying theory please see Lecture notes for chapter 5
To use this program: click “raw page source” in the left column, copy-paste in the Matlab/Octave editor, and run.
This program solves the linear stability problem for the Rayleigh-Bénard instability of a horizontal layer of fluid heated from below. This program is adapted from /sandbox/easystab/rayleigh_benard.m, which solved the problem in the case of slip conditions (“Free-Free boundaries”) and validated the approach by comparing with analytical results which exist in this case. The present program considers the more physical case of no-slip conditions at the walls (“Rigid-Rigid boundaries”).
The theory is presented in lecture 5 of the M2 DET course. The problem is set in generalised eigenvalue form \lambda B X = A X. The construction of the matrices and resolution is done by function RB(k,Ra,Pr)
@author: David Fabre
This program is protected by Creative Commons Licence CC-BY-NC
import EasyStab as ES # coeur de la toolbox easystab
import matplotlib.pyplot as plt # Pour tracer
import numpy as np # Pour calculer
from numpy import* # Pour construire les tableaux
from matplotlib.pyplot import* # Pour construire le graphe
from scipy.linalg import eig # Pour resoudre pb generalise
from scipy.sparse.linalg import eigs # Pour resoudre pb generalise avec methode iterative
from scipy.optimize import root_scalar
from scipy.linalg import block_diag
plt.close('all')
#%% Definition of functionsFUNCTION RB
Here is the definition of the function RB which performs the eigenvalue computation. Note that this function is designed so that it can be used in two ways :
[s,U] = RB(k,Ra,P) will return a vector s containing the 10 leading eigenvalues and an array U containing (in column) the corresponding eigenvectors.
lambdamax = RB(k,Ra,P) will return only one value corresponding to the leading eigenvalue. This is useful to use this function with fzero.
def RB(k,Ra,Pr):
#% renaming the differentiation matrices
Z=np.zeros((n,n));
I=np.eye(n);
dx=1j*k*I; dxx=-k**2*I;
Delta=dxx+dyy;System matrices
As explained in the lecture notes, after nondimensionalization the system of equations can be written in a matrix form \lambda B q =A q with the matrices q=\left(\begin{array}{c} u \\ v\\ p\\ \theta \end{array}\right) , \quad A=\left(\begin{array}{cccc} Pr \Delta&0&-\partial_x&0\\ 0&Pr \Delta&-\partial_y& Pr \\ \partial_x&\partial_y&0&0\\ 0& Ra &0&\Delta \end{array}\right) , \quad B=\left(\begin{array}{cccc}1&0&0&0\\0&1&0&0\\0&0&0&0\\0&0&0&1\end{array}\right)
NB in this program we use a slightly different nodimensionalization in which the velocities are resclaed by \sqrt(Re); this is equivalent.
#% system matrices
A=np.block(
[ [ Pr*Delta, Z, -sqrt(Ra)*dx, Z] ,
[ Z, Pr*Delta, -sqrt(Ra)*dy, Pr*sqrt(Ra)*I] ,
[ dx, dy, Z, Z] ,
[ Z, sqrt(Ra)*I, Z, Delta] ] )
B = block_diag(I,I,Z,I);Boundary conditions
The natural conditions are that u and v are zero at the walls, and the temperature perturbation \theta is also zero at the walls (the temperature is imposed at the wall, without perturbations).
\begin{array}{l} u|_0=0\\ u|_L=0\\ v|_0=0\\ v|_L=0\\ \theta|_0=0\\ \theta|_L=0\\ \end{array} thus the boundary conditions are expressed Cq=0, with the constraint matrix
C=\left(\begin{array}{cccc} I|_0&0&0&0\\ I|_L&0&0&0\\ 0&I|_L&0&0\\ 0&I|_0&0&0\\ 0&0&0&I|_L\\ 0&0&0&I|_0\\ \end{array}\right)
#% boundary conditions
II=eye(4*n);
u0=0; uL=n-1; v0=n; vL=2*n-1; T0=3*n; TL=4*n-1;
loc=(u0,uL,v0,vL,T0,TL);
C=II[loc,:]; A[loc,:]=C;
B[loc,:]=0;Computing eigenmodes
In the way the linear system \lambda B q=Aq hzs been constucted, the matrix B is not invertible, so we solve a generalized eigenvalue problem which will have infinite eigenvalues. We remove them form the results.
#% computing eigenmodes
s,em = eig(A,B);
#% sort eigenalues
S = s[abs(s)<1e8];
EM = em[:,abs(s)<1e8];
order = np.argsort(real(-S))
S = S[order];
EM = EM[:,order];
return S,EM
# function returning only leading eigenvalue (useful for building neutral curve)
def leading_eigenvalue(k,Ra,Pr):
S,EM = RB(k,Ra,Pr)
return S[0]
# Function to plot eigenmodes
def plotmode(event,mode,S,k):
# This function is used to represent the structure of an eigenmode
global y,dy,dyy
n = y.shape[0];
u = mode[0:n];
v = mode[n:2*n];
p = mode[2*n:3*n];
T = mode[3*n:4*n];
MM =max(abs(mode))
vorticity = -(dy@u)+1j*k*v;
fig, (ax1, ax2) = plt.subplots(1, 2, gridspec_kw={'width_ratios': [1, 2]})
ax1.plot(real(u),y,'b-',label='Re(u)');ax1.plot(imag(u),y,'b--',label='Im(u)');
ax1.plot(real(v),y,'g-',label='Re(v)');ax1.plot(imag(v),y,'g--',label='Im(v)');
ax1.plot(real(p),y,'k-',label='Re(p)');ax1.plot(imag(p),y,'k--',label='Im(p)');
ax1.plot(real(T),y,'r-',label='Re(T)');ax1.plot(imag(T),y,'r--',label='Im(T)');
ax1.set_ylabel('y');ax1.set_xlim(-MM*1.2,MM*1.2);
ax1.set_ylim(0,1);
ax1.legend(loc='lower center',fontsize=9,ncol=2)
if (-imag(S)/k)<1 and (-imag(S)/k)>0:
ycrit = sqrt(1+imag(S)/k);
ax1.plot([-MM,MM],[ycrit,ycrit],'k:',[-MM,MM],[-ycrit,-ycrit],'k:')
Lx=2*pi/k; Nx =30; x=linspace(-Lx/2,Lx/2,Nx);
yy = y;
pp = 2*real(np.outer(p,exp(1j*k*x)));
uu=2*real(np.outer(u,exp(1j*k*x)));
uu
vv=2*real(np.outer(v,exp(1j*k*x)));
TT=2*real(np.outer(T,exp(1j*k*x)));
vorticityvorticity=2*real(np.outer(vorticity,exp(1j*k*x)))
ax2.contourf(x,yy,TT,10);
ax2.quiver(x,yy,uu,vv,color='k');
ax2.set_xlabel('x');
z_str = f"{S.real:.3f} + {S.imag:.3f}j"
plt.title("Structure of the eigenmode \n (Temperature component + velocity)\n" +" lambda = "+z_str,fontsize=10);
# Function to link the previous functin with the figure 1
def on_pick(event):
global EM
ind = event.ind[0] # Index du point cliqué
EMM =EM[:,ind]
z_str = f"{S[ind].real:.3f} + {S[ind].imag:.3f}j"
print("Clic sur valeur propre "+str(ind)+" : Eigenvalue = "+z_str+ " ind " +str(ind))
plotmode(event, EMM, S[ind], k)
plt.show();plt.pause(0.01);
#%% Main program
#% parameters for the discretization
Nx = int(ES.myinput("==> Nx, number of points ? ",60)); # order of discretization
n = Nx+1 # number of grid points
DiscetizationType = ES.myinput("==> Discretization type ? ","Cheb")
#% building differentiation matriCes
y,dy,dyy,w = ES.dif1D(DiscetizationType,parameters={"a":0,"b":1},N=Nx);
#%% Computing one spectrum for one set of parameters
print("\n Chapter 1 : compute one spectrum and plot the eigenvalues/eigenmodes \n")
# Selecting the parameters
k = float(ES.myinput("==> Wavenumber k ? ",3.117));
Ra = float(ES.myinput("==> Rayleigh number ? ",1708));
Pr = float(ES.myinput("==> Prandtl number ? ",10));
# NB : According to the litterature, the neutral conditions are
# Ra = 1708 and k = 3.117 whatever the value of Pr
# compute spectrum
print(" (Computing spectrum)")
S,EM = RB(k,Ra,Pr);
#We plot the spectrum in figure 1
fig, ax = plt.subplots(1)
h, = plt.plot(imag(S),real(S),'go',picker=5);
fig.canvas.mpl_connect('pick_event', on_pick) # to associate the previously defined function to click events
plt.plot([-1, 1],[0,0],'k:');
plt.ylabel('lambda_r');ylim([-200, 50]);
plt.xlabel('lambda_i');xlim([-1,1]);
plt.title('Spectrum for Ra ='+str(Ra)+' ; k = '+f"{k:.4f}"+' ; Pr = '+str(Pr))
plt.show(); plt.pause(0.01)
# plot the leading eigenmode in figure 1
plotmode(1,EM[:,0],S[0],k);
plt.figure(2);plt.show();plt.pause(0.1);
print(" => see spectrum in figure 1 and leading mode in figure 2 ")
print(" (click on figure 1 to plot other eigenmodes) ")
#%% Next : parametric studyFirst we compute \lambda(k) for several values of Ra and plot the results in figure 3.
rep = ES.myinput("\n Do you want to draw curves lambda(k) for several Ra ?","yes")
if rep=="yes":
plt.figure();
for Ra in linspace(1500,2500,5):
print("Ra = "+str(Ra)+" :")
ktab = linspace(1,5,41)
smaxtab = []
for k in ktab:
s,U = RB(k,Ra,Pr);
smaxtab.append(real(s[0]));
plt.plot(ktab,smaxtab,label="Ra = "+str(Ra));
plt.show;plt.pause(0.001);
plt.plot(ktab,0*ktab,'k:')
plt.legend();
plt.xlabel('k');plt.ylabel('lambda')
#%% Marginal stability curveWe now build the marginal stability curve Ra_c(k) corresponding to the location in the [Ra,k]-plane where the leading eigenvalue is exactly zero.
For this we do a loop over k and look for the location where \lambda_{max} is exactly zero, using function “root_scalar” Results are plotted in figure 4.
rep = ES.myinput("\n Do you want to Reconstuct the marginal stability curve in the (Re,k) plane ? ","yes")
if rep=="yes":
Ra = 3000;
Ratab=[];
ktab = linspace(1.5,6,46);
for k in ktab:
sol = root_scalar(lambda a: np.real(leading_eigenvalue(k, a,Pr)),
x0=Ra, method='newton')
Ratab.append(sol.root);
print("k = " +str(k)+" ; Ra = "+str(sol.root))
#%% Draw the figure
plt.figure();
plt.plot(ktab,Ratab);
plt.xlabel('k');plt.ylabel('Ra');
plt.ylim((1000,3000));
plt.title('Neutral curve');
plt.text(2, 1300, "Stable")
plt.text(3.5, 2500, "Unstable")
plt.show()
