In [8]:
import numpy as np
import matplotlib.pyplot as plt
##from matplotlib.patches import Rectangle
import sys
# La reaction étudiée est aA + bB ⇄ cC + dD
# Coefficients stœchiométriques
a, b, c, d = 1, 1, 1, 1
# Quantités de matière initiales
n_Ai, n_Bi, n_Ci, n_Di = 0.1, 0.1, 0.4, 0 # En mol.
# Volume du milieu réactionnel − Constant
V = 100.0e-3 # 100 mL
# Constante d'equilibre
K = 4
########################
### FONCTIONS UTILES ###
########################
def interversion_reactifs_produits_initial() :
global a,b,c,d,n_Ai,n_Bi,n_Ci,n_Di,K
a,c = c,a
b,d = d,b
n_Ai,n_Ci = n_Ci,n_Ai
n_Bi,n_Di = n_Di,n_Bi
K = 1 / K
def interversion_reactifs_produits_final() :
# Ces variables sont des tableaux numpy
global n_A,n_B,n_C,n_D,Q_r
n_A,n_C = n_C,n_A
n_B,n_D = n_D,n_B
Q_r = 1 / Q_r
def annoter(histogramme,axes):
for bar in histogramme :
axes.text(bar.get_x() + bar.get_width() / 2, bar.get_height(),
f'{bar.get_height():.3f}', ha='center', va='bottom', fontsize=10)
########################
### SENS D'ÉVOLUTION ###
########################
print("La constante d'équilibre vaut K =",K)
# Évacuation des 9 cas impossibles, c'est à dire où au moins un réactif et au moins un produit sont absents OU des erreurs triviales
if ((n_Ai == 0 and n_Ci == 0) or (n_Ai == 0 and n_Di == 0) or (n_Bi == 0 and n_Ci == 0) or (n_Bi == 0 and n_Di == 0) \
or (n_Ai < 0) or (n_Bi < 0) or (n_Ci < 0) or (n_Di < 0)) :
print ("Pas de réaction possible avec ces quantités initiales. Le script va s'arrêter.")
sys.exit()
# Détermination du sens de la réaction dans les 7 cas possibles
# Cas 1 Toutes les espèces sont présentes
if (n_Ai != 0 and n_Bi != 0 and n_Ci != 0 and n_Di != 0) :
# Calcul Q_r initial
Q_ri = ((n_Ci/V)**c)*((n_Di/V)**d)/(((n_Ai/V)**a)*((n_Bi/V)**b))
print("Le quotient de réaction à l'état initial vaut: Q_r initial =",Q_ri, end='') # Sans passage à la ligne
if (Q_ri == K) :
print(" = K ⇒ Le système n'évolue pas, il est déjà à l'équilibre.")
sens = 0
elif (Q_ri < K) :
print(" < K ⇒ Le système évolue dans le sens direct.")
sens = 1
else : # Q_ri > K
print(" > K ⇒ Le système évolue dans le sens indirect.")
sens = 2
interversion_reactifs_produits_initial() # Pour effectuer les calculs
# Cas 2 Il manque au moins un produit
elif (n_Ai != 0 and n_Bi != 0) :
print ("Q_r initial = 0 ⇒ Le système évolue nécessairement dans le sens direct.")
sens = 1
# Cas 3 Il manque au moins un réactif
elif (n_Ci != 0 and n_Di != 0) :
print ("Le Q_r initial est infini ⇒ Le système évolue nécessairement dans le sens indirect.")
sens = 2
interversion_reactifs_produits_initial() # Pour effectuer les calculs
#############################################
### CALCULS EFFECTUÉS DANS LE SENS DIRECT ###
#############################################
# Détermination de l'avancement maximal
xmax = min(n_Ai/a, n_Bi/b)
# Valeur de ε
ε = xmax / 1E4
# Quasiment toutes les variables sont des tableaux numpy.
# Toutes les valeurs (avancement et qté de matières, quotient) sont calculées en une fois, sans boucle
# Initialisation des valeurs de l'avancement x entre 0 mol et xmax <= Sans se préoccuper de l'avancement final x_f
nombre_de_points = 1001
x = np.linspace(0, xmax, nombre_de_points)
# Taux d'avancement
tau = x/xmax # Compris entre 0 et 1
# Quantités de matière
n_A = n_Ai - a*x
n_A = np.where(n_A != 0, n_A, ε) # Si n_A non nulle, elle est conservée sinon ε
n_B = n_Bi - b*x
n_B = np.where(n_B != 0, n_B, ε)
n_C = n_Ci + c*x
n_C = np.where(n_C != 0, n_C, ε)
n_D = n_Di + d*x
n_D = np.where(n_D != 0, n_D, ε)
# Quotient de réaction
Q_r = ((n_C/V)**c)*((n_D/V)**d)/(((n_A/V)**a)*((n_B/V)**b))
# Étude de l'équilibre : détermination de l'indice i = i_f correspondant à l'équilibre
i = 0
if (sens != 0) :
while (Q_r[i] <= K) :
global i_f
i_f = i
i = i + 1
else :
i_f = 0
# Si le sens a été inversé, retour à la situation d'origine
# L'avancement x et le taux d'avancement tau sont conservés, mais affichés en signalant le sens
if (sens == 2) :
interversion_reactifs_produits_initial()
interversion_reactifs_produits_final()
####################
### Histogrammes ###
####################
especes = ['$n_A$', '$n_B$', '$n_C$', '$n_D$']
quantites_initiales = [n_Ai, n_Bi, n_Ci, n_Di]
quantites_finales = [n_A[i_f], n_B[i_f], n_C[i_f], n_D[i_f]]
n_Af, n_Bf, n_Cf, n_Df = quantites_finales # Pour déterminer l'échelle commune
bar_colors = ['C0', 'C0', 'C4', 'C4']
fig, [ax1, ax2] = plt.subplots(figsize=(14,8), nrows=1, ncols=2)
ylim = max(n_Ai, n_Bi, n_Ci, n_Di, n_Af, n_Bf, n_Cf, n_Df) # Échelle commune
ax1.set_ylim(0, ylim*1.05)
ax2.set_ylim(0, ylim*1.05)
ax1.set_ylabel("Quantités de matière (mol.)")
ax2.set_ylabel("Quantités de matière (mol.)")
histo1 = ax1.bar(especes,quantites_initiales, color=bar_colors) # Génère les barres
ax1.set_title("Quantités de matière à l’état initial")
annoter(histo1,ax1)
histo2 = ax2.bar(especes,quantites_finales, color=bar_colors)
ax2.set_title("Quantités de matière à l’état final")
annoter(histo2,ax2)
colors = {'Réactifs':'C0', 'Produits':'C4'}
labels = list(colors.keys())
handles = [plt.Rectangle((0,0),1,1, color=colors[label]) for label in labels]
ax1.legend(handles, labels)
ax2.legend(handles, labels)
# Export image
fig.savefig('Histogrammes.png')
### Affichage du taux d'avancement
if (sens == 1) :
print ("Le taux d’avancement à l’équilibre est τ =",f'{tau[i_f]:.2f}',"dans le sens direct.")
fig.suptitle("Évolution dans le sens direct")
elif (sens == 2) :
print ("Le taux d’avancement à l’équilibre est τ =",f'{tau[i_f]:.2f}',"dans le sens indirect.")
fig.suptitle("Évolution dans le sens indirect")
else :
print ("Pas d’avancement, le système reste à l’équilibre : τ =",f'{tau[i_f]:.2f}')
############################################
### Variations des quantités de matières ###
############################################
fig, [ax1, ax2] = plt.subplots(figsize=(14,7), nrows=1, ncols=2)
if (sens == 0) :
print("Pas d'évolution des quantités de matières.")
else :
if (sens == 2) : # Inversion de l'axe des abscisses pour le sens indirect
x_inf = max(x)
x_sup = min(x)
fig.suptitle("Évolution dans le sens indirect")
x_legend = "Avancement x (mol.) − Graduations inversées"
else :
x_inf = min(x) # Sens classique de l'axe des abscisses pour le sens direct
x_sup = max(x)
fig.suptitle("Évolution dans le sens direct")
x_legend = "Avancement x (mol.)"
ax1.set_xlim(x_inf, x_sup)
ax1.set_title("Variations des quantités de matière")
ax1.set_xlabel(x_legend)
ax1.set_ylabel("Quantités de matière (mol.)")
### Quantités de matières
ax1.plot(x[:i_f], n_A[:i_f], 'C0:', label='$n_A$', linewidth=2.5) # Avant l'équilibre
ax1.plot(x[i_f:], n_A[i_f:], 'C7:', linewidth=1) # Après l'équilibre
ax1.plot(x[:i_f], n_B[:i_f], 'C0', label='$n_B$', linewidth=0.75)
ax1.plot(x[i_f:], n_B[i_f:], 'C7:', linewidth=1)
ax1.plot(x[:i_f], n_C[:i_f], 'C4:', label='$n_C$', linewidth=2.5)
ax1.plot(x[i_f:], n_C[i_f:], 'C7:', linewidth=1)
ax1.plot(x[:i_f], n_D[:i_f], 'C4', label='$n_D$', linewidth=0.75)
ax1.plot(x[i_f:], n_D[i_f:], 'C7:', linewidth=1)
ax1.axvline(x = x[i_f], ls ="--", color = 'salmon', label = '$x_f$') # Droite x = x_final
ax1.legend()
##########################################
### Variations du quotient de réaction ###
##########################################
ax2.set_xlim(x_inf, x_sup)
ax2.set_yscale('log')
ax2.set_title("Variation du quotient de réaction")
ax2.set_xlabel(x_legend)
ax2.set_ylabel("Quotient de réaction")
ax2.plot(x[:i_f], Q_r[:i_f], 'C0', label='$Q_R$', linewidth=1) # Avant l'équilibre
ax2.plot(x[i_f+1:], Q_r[i_f+1:], 'C7:', linewidth=1) # Après l'équilibre
ax2.axvline(x = x[i_f], ls ="--", color = 'salmon', label = '$x_f$')
ax2.plot(x, K*np.ones(len(x)), color = 'C8', label='$K$')
ax2.legend()
# Export image
fig.savefig('Variations.png') ;
La constante d'équilibre vaut K = 4 Q_r initial = 0 ⇒ Le système évolue nécessairement dans le sens direct. Le taux d’avancement à l’équilibre est τ = 0.37 dans le sens direct.
In [ ]: