|
import numpy as np |
|
import matplotlib |
|
import matplotlib.pyplot as plt |
|
import seaborn as sns |
|
import matplotlib as mpl |
|
from matplotlib.ticker import LogLocator |
|
|
|
def create_log_law_plot( |
|
ax=None, |
|
xlim=(1e-5, 1e-2), |
|
ylim=None, |
|
data=None, |
|
A_values=None, |
|
A_labels=None, |
|
show_grid=True, |
|
show_annotation=True, |
|
log_spacing=True, |
|
title="Profil de vitesse avec émagramme pour analyse de ∂u/∂y"): |
|
""" |
|
Crée ou transforme un graphique existant en émagramme pour l'analyse de cisaillement pariétal. |
|
|
|
Parameters: |
|
----------- |
|
ax : matplotlib.axes.Axes, optional |
|
Axes existant à transformer. Si None, un nouvel axe est créé. |
|
xlim : tuple, optional |
|
Les limites de l'axe x (distance à la paroi), par défaut (1e-5, 1e-2). |
|
ylim : tuple, optional |
|
Les limites de l'axe y (vitesse). Si None, elles sont calculées à partir des données. |
|
data : list of dict, optional |
|
Liste de dictionnaires contenant les données à tracer. Chaque dict doit contenir: |
|
- 'x': array-like, données x (distance y) |
|
- 'y': array-like, données y (vitesse u) |
|
- 'kwargs': dict, arguments pour plt.plot (marker, color, label, etc.) |
|
A_values : list, optional |
|
Liste des valeurs de gradient à tracer comme lignes diagonales. |
|
A_labels : list, optional |
|
Sous-ensemble de A_values pour lesquelles afficher les étiquettes. |
|
show_grid : bool, optional |
|
Si True, affiche une grille de référence. |
|
show_annotation : bool, optional |
|
Si True, ajoute une annotation explicative. |
|
title : str, optional |
|
Titre du graphique. |
|
|
|
Returns: |
|
-------- |
|
fig : matplotlib.figure.Figure |
|
La figure contenant le graphique. |
|
ax : matplotlib.axes.Axes |
|
L'axe principal avec l'émagramme. |
|
ax2 : matplotlib.axes.Axes |
|
L'axe secondaire avec les étiquettes de gradient. |
|
""" |
|
|
|
# Créer un nouvel axe si nécessaire |
|
if ax is None: |
|
fig, ax = plt.subplots(figsize=(12, 10)) |
|
else: |
|
fig = ax.figure |
|
|
|
# Valeurs par défaut pour A_values et A_labels |
|
if A_values is None: |
|
if log_spacing: |
|
A_values = [ |
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, |
|
100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, |
|
4000, 5000, 6000, 7000, 8000, 9000, 10000, 2e4, 3e4, 4e4, 5e4, |
|
6e4, 7e4, 8e4, 9e4, 1e5, 2e5, 3e5, 4e5, 5e5, 6e5, 7e5, 8e5, |
|
9e5, 1e6 |
|
] |
|
else: |
|
A_values = [ |
|
10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000 |
|
] |
|
if A_labels is None: |
|
if log_spacing: |
|
A_labels = [1, 10, 100, 1000, 10000, 100000, 1000000] |
|
else: |
|
A_labels = [100, 500, 1000, 5000, 10000] |
|
|
|
# Configurer les échelles logarithmiques |
|
ax.set_xscale("log") |
|
ax.set_yscale("log") |
|
ax.set_xlim(xlim) |
|
|
|
# Tracer les données si fournies |
|
if data is not None: |
|
for dataset in data: |
|
x = dataset.get('x', []) |
|
y = dataset.get('y', []) |
|
kwargs = dataset.get('kwargs', {}) |
|
ax.plot(x, y, **kwargs) |
|
|
|
# Calculer ylim à partir des données si non spécifié |
|
if ylim is None and data is not None: |
|
all_y_values = [] |
|
for dataset in data: |
|
all_y_values.extend(dataset.get('y', [])) |
|
if all_y_values: |
|
ymin = max(0.8 * min(all_y_values), 1e-3) |
|
ymax = 1.2 * max(all_y_values) |
|
ylim = (ymin, ymax) |
|
else: |
|
ylim = (1e-2, 1e2) # Valeur par défaut si pas de données |
|
elif ylim is None: |
|
ylim = (1e-2, 1e2) # Valeur par défaut si pas de données |
|
|
|
ax.set_ylim(ylim) |
|
|
|
# Tracer les lignes u=A*y |
|
for A in A_values: |
|
# Générer les points pour la ligne u=A*y |
|
x_line = np.logspace(np.log10(xlim[0]), np.log10(xlim[1]), 100) |
|
y_line = A * x_line |
|
|
|
# Déterminer le style de ligne basé sur la valeur de A |
|
if A in A_labels: |
|
linestyle = '-' |
|
linewidth = 1.0 |
|
alpha = 0.5 |
|
color = 'black' |
|
else: |
|
linestyle = '--' |
|
linewidth = 0.7 |
|
alpha = 0.5 |
|
color = 'gray' |
|
|
|
# Tracer la ligne |
|
ax.plot(x_line, |
|
y_line, |
|
linestyle=linestyle, |
|
linewidth=linewidth, |
|
alpha=alpha, |
|
color=color, |
|
zorder=1) |
|
|
|
# Ajouter une étiquette à la ligne si c'est une ligne principale |
|
if A in A_labels: |
|
# Trouver un point approprié pour placer l'étiquette (au milieu de la ligne visible) |
|
mask = (y_line >= ylim[0]) & (y_line <= ylim[1]) |
|
if np.any(mask): |
|
idx = np.where(mask)[0][len(np.where(mask)[0]) // 2] |
|
x_label = x_line[idx] |
|
y_label = y_line[idx] |
|
|
|
# Dans un graphique log-log, u=A*y est une ligne droite avec une pente de 1 |
|
angle = 45 # degrés |
|
|
|
# Créer une transformation pour placer et tourner le texte |
|
trans_angle = plt.gca().transData.transform_angles( |
|
np.array([angle]), np.array([[x_label, y_label]]))[0] |
|
|
|
# Ajouter l'étiquette |
|
ax.text(x_label, |
|
y_label, |
|
f"$\\alpha={A}$", |
|
fontsize=8, |
|
ha='center', |
|
va='center', |
|
rotation=angle, |
|
rotation_mode='anchor', |
|
bbox=dict(facecolor='white', |
|
alpha=0.7, |
|
edgecolor='none', |
|
pad=1), |
|
zorder=0) |
|
|
|
# Ajouter une grille de référence |
|
if show_grid: |
|
# Grille verticale (valeurs y constantes) |
|
y_grid = np.logspace(np.log10(xlim[0]), np.log10(xlim[1]), 7) |
|
for y in y_grid: |
|
ax.axvline(y, |
|
color='lightgray', |
|
linestyle='-', |
|
linewidth=0.5, |
|
alpha=0.5, |
|
zorder=0) |
|
|
|
# Grille horizontale (valeurs u constantes) |
|
u_grid = np.logspace(np.log10(ylim[0]), np.log10(ylim[1]), 7) |
|
for u in u_grid: |
|
ax.axhline(u, |
|
color='lightgray', |
|
linestyle='-', |
|
linewidth=0.5, |
|
alpha=0.5, |
|
zorder=0) |
|
|
|
# Ajouter les étiquettes d'axes et le titre |
|
ax.set_xlabel("y (m)", fontsize=12) |
|
ax.set_ylabel("u (m/s)", fontsize=12) |
|
ax.set_title(title, fontsize=14) |
|
|
|
# Ajouter une annotation explicative |
|
if show_annotation: |
|
ax.text( |
|
0.02, |
|
0.02, |
|
r"Lignes diagonales: $u = \alpha\,y $, où $\alpha$ est la pente", |
|
transform=ax.transAxes, |
|
fontsize=10, |
|
va='bottom', |
|
bbox=dict(facecolor='white', |
|
alpha=0.7, |
|
edgecolor='gray', |
|
boxstyle='round,pad=0.5')) |
|
|
|
# Ajouter un axe secondaire pour montrer explicitement les valeurs de A |
|
ax2 = ax.twinx() |
|
ax2.set_yscale('log') |
|
ax2.set_ylim(ylim) |
|
ax2.set_ylabel("Gradient de vitesse ∂u/∂y", fontsize=12) |
|
|
|
# Aligner les ticks avec les valeurs A |
|
tick_positions = [ |
|
A * xlim[1] for A in A_labels |
|
if A * xlim[1] <= ylim[1] and A * xlim[1] >= ylim[0] |
|
] |
|
ax2.set_yticks(tick_positions) |
|
ax2.set_yticklabels([f"{int(pos/xlim[1])}" for pos in tick_positions]) |
|
|
|
# Configurer les locators pour les ticks |
|
ax.xaxis.set_major_locator(LogLocator(base=10.0)) |
|
ax.yaxis.set_major_locator(LogLocator(base=10.0)) |
|
|
|
# Améliorer l'apparence |
|
plt.grid(False) # Désactiver la grille par défaut |
|
plt.tight_layout() |
|
|
|
return fig, ax, ax2 |