tiffank1802 commited on
Commit
24a41d8
·
1 Parent(s): 4188d0a

Deploy Dang Van fatigue analysis app to Hugging Face Spaces

Browse files

Features:
- Gradio web interface with École Centrale Lyon styling
- Real-time Dang Van criterion calculations
- Interactive visualizations and parameter controls
- Data export capabilities (CSV and PNG)
- Support for uniaxial and torsion loading

Files:
- app.py: Main Gradio application
- versDV.py: Dang Van calculation functions
- deviatoire.py: Deviatoric stress calculations
- requirements.txt: Python dependencies

Files changed (6) hide show
  1. README.md +113 -11
  2. app.py +344 -0
  3. app_gradio.py +344 -0
  4. deviatoire.py +99 -0
  5. requirements.txt +5 -0
  6. versDV.py +342 -0
README.md CHANGED
@@ -1,13 +1,115 @@
1
- ---
2
- title: DangVan
3
- emoji: 🏃
4
- colorFrom: purple
5
- colorTo: indigo
6
- sdk: gradio
7
- sdk_version: 6.3.0
8
- app_file: app.py
9
- pinned: false
10
- license: mit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # Critère de Dang Van - Analyse de Fatigue
2
+ **École Centrale Lyon**
3
+
4
+ ![École Centrale Lyon](https://images.seeklogo.com/logo-png/48/2/ecole-centrale-de-lyon-logo-png_seeklogo-481949.png)
5
+
6
+ ## Description
7
+
8
+ Cette application web interactive permet d'analyser le comportement en fatigue des matériaux selon le critère de Dang Van. Développée à l'École Centrale Lyon, elle visualise le domaine de sécurité défini par ce critère pour différents types de chargements complexes.
9
+
10
+ ## 🎯 Objectifs
11
+
12
+ - **Analyse multiaxiale de fatigue** : Appliquer le critère de Dang Van pour prédire l'apparition de fissures
13
+ - **Visualisation interactive** : Génération de diagrammes de Dang Van en temps réel
14
+ - **Comparaison de chargements** : Étude comparative entre traction-compression et torsion
15
+ - **Export de données** : Téléchargement des résultats au format CSV et PNG
16
+
17
+ ## 🧮 Fonctionnalités
18
+
19
+ ### Calcul et Analyse
20
+ - **Génération de tenseurs** : Création de tenseurs de contraintes pour différents chargements
21
+ - **Calcul de pression hydrostatique** : Détermination de la composante sphérique du tenseur
22
+ - **Amplitude de cisaillement maximale** : Recherche du cisaillement maximal sur toutes les facettes
23
+ - **Critère de Dang Van** : Application du critère multiaxial de fatigue
24
+
25
+ ### Visualisation
26
+ - **Diagrammes interactifs** : Graphiques de pression hydrostatique vs amplitude de cisaillement
27
+ - **Personnalisation** : Options de thèmes, de grille et de taille de points
28
+ - **Résultats statistiques** : Métriques détaillées sur les calculs effectués
29
+
30
+ ### Export
31
+ - **Données brutes** : Export CSV des données de traction-compression et torsion
32
+ - **Graphiques** : Téléchargement des diagrammes en haute résolution PNG
33
+
34
+ ## 🚀 Déploiement
35
+
36
+ Cette application est optimisée pour le déploiement sur **Hugging Face Spaces** avec Gradio.
37
+
38
+ ### Installation locale
39
+
40
+ ```bash
41
+ # Cloner le dépôt
42
+ git clone <repository-url>
43
+ cd APP_DangVan
44
+
45
+ # Installer les dépendances
46
+ pip install -r requirements.txt
47
+
48
+ # Lancer l'application
49
+ python app_gradio.py
50
+ ```
51
+
52
+ ### Déploiement sur Hugging Face Spaces
53
+
54
+ 1. **Créer un nouvel Espace** sur [Hugging Face](https://huggingface.co/spaces)
55
+ 2. **Choisir Gradio SDK**
56
+ 3. **Uploader les fichiers** du projet
57
+ 4. **L'application se déploie automatiquement**
58
+
59
+ ## 📋 Structure du projet
60
+
61
+ ```
62
+ APP_DangVan/
63
+ ├── app_gradio.py # Application Gradio principale
64
+ ├── versDV.py # Fonctions de calcul Dang Van
65
+ ├── deviatoire.py # Calculs de déviateurs
66
+ ├── requirements.txt # Dépendances Python
67
+ ├── README.md # Documentation
68
+ └── .streamlit/ # Configuration Streamlit (non utilisé pour Gradio)
69
+ ```
70
+
71
+ ## 👥 Équipe
72
+
73
+ **Étudiants :**
74
+ - Kevin TONGUE
75
+ - Paul LORTHIOIR
76
+
77
+ **Encadrement :**
78
+ - Éric FEULVACH
79
+ - Françoise FAUVIN
80
+
81
+ **UE :** Projet de recherche et innovation
82
+ **Thème :** Analyse en fatigue de structures industrielles soumises à des chargements complexes
83
+ **Date :** 2026
84
+
85
+ ## 📚 Théorie du critère de Dang Van
86
+
87
+ Le critère de Dang Van est un critère multiaxial de fatigue à haute durée de vie qui s'exprime sous la forme :
88
+
89
+ $$\\tau_{a,max} + \\alpha p_h \\leq \\beta$$
90
+
91
+ où :
92
+ - $\\tau_{a,max}$ est l'amplitude maximale de cisaillement
93
+ - $p_h$ est la pression hydrostatique
94
+ - $\\alpha$ et $\\beta$ sont des constantes matériau
95
+
96
+ Ce critère permet de prendre en compte l'effet de la pression hydrostatique sur l'endurance en fatigue des matériaux métalliques.
97
+
98
+ ## 🛠️ Technologies utilisées
99
+
100
+ - **Python** : Langage de programmation principal
101
+ - **Gradio** : Interface web interactive
102
+ - **NumPy** : Calculs numériques
103
+ - **Matplotlib** : Visualisation graphique
104
+ - **SciPy** : Calculs scientifiques avancés
105
+ - **Pandas** : Manipulation de données
106
+
107
+ ## 📝 Licence
108
+
109
+ Projet académique - École Centrale Lyon
110
+ Mécanique des Matériaux | UE: Fatigue et Fissuration
111
+ © 2024 - Tous droits réservés
112
+
113
  ---
114
 
115
+ **École Centrale Lyon** | Laboratoire de Mécanique des Matériaux | Projet de recherche et innovation
app.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import versDV as dv
5
+ import deviatoire as dev
6
+ from math import pi
7
+ import pandas as pd
8
+ import io
9
+ import base64
10
+
11
+ # CSS personnalisé avec les couleurs de l'école
12
+ css = """
13
+ /* Palette de couleurs Centrale Lyon (version rouge) */
14
+ :root {
15
+ --primary-red: #D52B1E;
16
+ --secondary-red: #B22222;
17
+ --accent-red: #8B0000;
18
+ --light-gray: #F5F5F5;
19
+ --dark-gray: #333333;
20
+ }
21
+
22
+ .gradio-container {
23
+ font-family: 'Arial', sans-serif;
24
+ }
25
+
26
+ .main-header {
27
+ background: linear-gradient(90deg, var(--primary-red) 0%, var(--secondary-red) 100%);
28
+ padding: 1.5rem;
29
+ border-radius: 0 0 10px 10px;
30
+ color: white;
31
+ text-align: center;
32
+ margin-bottom: 2rem;
33
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
34
+ }
35
+
36
+ .centrale-title {
37
+ font-family: 'Georgia', serif;
38
+ font-weight: 700;
39
+ margin-bottom: 0.5rem;
40
+ }
41
+
42
+ .centrale-subtitle {
43
+ font-family: 'Arial', sans-serif;
44
+ font-size: 1.2rem;
45
+ opacity: 0.9;
46
+ }
47
+
48
+ .centrale-card {
49
+ border-left: 4px solid var(--accent-red);
50
+ padding: 1rem;
51
+ background-color: var(--light-gray);
52
+ border-radius: 0 8px 8px 0;
53
+ margin: 1rem 0;
54
+ }
55
+
56
+ .footer {
57
+ background-color: var(--dark-gray);
58
+ color: white;
59
+ padding: 1rem;
60
+ text-align: center;
61
+ margin-top: 2rem;
62
+ border-radius: 8px 8px 0 0;
63
+ font-size: 0.9rem;
64
+ }
65
+ """
66
+
67
+ def create_header():
68
+ """Crée le header HTML avec le style Centrale Lyon"""
69
+ return f"""
70
+ <div class="main-header">
71
+ <h1 class="centrale-title">ÉCOLE CENTRALE LYON</h1>
72
+ <h2 class="centrale-subtitle">Analyse de Fatigue - Critère de Dang Van</h2>
73
+ </div>
74
+ """
75
+
76
+ def create_info_panel():
77
+ """Crée le panneau d'informations"""
78
+ return """
79
+ <div class="centrale-card">
80
+ <h3>📝 À propos du critère de Dang Van</h3>
81
+ <p>Le critère de Dang Van est un critère multiaxial de fatigue à haute durée de vie.
82
+ Il permet de prendre en compte l'effet de la pression hydrostatique sur l'endurance
83
+ en fatigue des matériaux métalliques. Cette application visualise le domaine de
84
+ sécurité défini par ce critère pour différents types de chargements.</p>
85
+
86
+ <h4>👥 Équipe</h4>
87
+ <p><strong>Étudiants :</strong> Kevin TONGUE, Paul LORTHIOIR</p>
88
+ <p><strong>Enseignants :</strong> Éric FEULVACH, Françoise FAUVIN</p>
89
+ <p><strong>UE :</strong> Projet de recherche et innovation</p>
90
+ <p><strong>Thème :</strong> Analyse en fatigue de structures industrielles soumises à des chargements complexes</p>
91
+ <p><strong>Date :</strong> 2026</p>
92
+ </div>
93
+ """
94
+
95
+ def calculate_dang_van(sigma1, omega, fin, pasTemps, point_size, show_grid, theme):
96
+ """
97
+ Fonction principale de calcul pour le critère de Dang Van
98
+ """
99
+ try:
100
+ # Configuration du style selon le thème choisi
101
+ if theme == "Moderne":
102
+ plt.style.use('seaborn-v0_8-darkgrid')
103
+ elif theme == "Scientifique":
104
+ plt.style.use('seaborn-v0_8-paper')
105
+ else:
106
+ plt.style.use('default')
107
+
108
+ # Calcul des points pour chargement uniaxial
109
+ points_uniaxial = dv.nuage(sigma1, omega, pasTemps, fin)
110
+
111
+ # Calcul des points pour torsion
112
+ points_torsion = dv.nuageOrt(sigma1, omega, pasTemps, fin)
113
+
114
+ # Préparation de la figure
115
+ fig, ax = plt.subplots(figsize=(10, 6))
116
+
117
+ # Tracé des points
118
+ scatter1 = ax.scatter(
119
+ points_uniaxial[:, 0],
120
+ points_uniaxial[:, 1],
121
+ s=point_size,
122
+ alpha=0.7,
123
+ label='Traction-Compression',
124
+ edgecolors='white',
125
+ linewidth=1
126
+ )
127
+
128
+ scatter2 = ax.scatter(
129
+ points_torsion[:, 0],
130
+ points_torsion[:, 1],
131
+ s=point_size,
132
+ alpha=0.7,
133
+ label='Torsion',
134
+ edgecolors='white',
135
+ linewidth=1
136
+ )
137
+
138
+ # Configuration des axes et titres
139
+ ax.set_xlabel("Pression hydrostatique (MPa)", fontsize=12, fontweight='bold')
140
+ ax.set_ylabel("Amplitude de cisaillement max (MPa)", fontsize=12, fontweight='bold')
141
+ ax.set_title("Diagramme de Dang Van - École Centrale Lyon",
142
+ fontsize=14, fontweight='bold', pad=20)
143
+
144
+ if show_grid:
145
+ ax.grid(True, linestyle='--', alpha=0.3)
146
+
147
+ ax.legend(loc='best', frameon=True, fancybox=True, shadow=True)
148
+
149
+ # Ajustement des limites
150
+ xlim_min = min(points_uniaxial[:, 0].min(), points_torsion[:, 0].min()) - 10
151
+ xlim_max = max(points_uniaxial[:, 0].max(), points_torsion[:, 0].max()) + 10
152
+ ylim_max = max(points_uniaxial[:, 1].max(), points_torsion[:, 1].max()) + 10
153
+ ax.set_xlim(xlim_min, xlim_max)
154
+ ax.set_ylim(0, ylim_max)
155
+
156
+ # Sauvegarde de la figure
157
+ buf = io.BytesIO()
158
+ fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
159
+ buf.seek(0)
160
+ plot_url = f"data:image/png;base64,{base64.b64encode(buf.read()).decode()}"
161
+ plt.close(fig)
162
+
163
+ # Calcul des statistiques
164
+ stats_uniaxial = len(points_uniaxial)
165
+ stats_torsion = len(points_torsion)
166
+ mean_hydro = np.mean(points_uniaxial[:, 0])
167
+ max_shear = max(points_uniaxial[:, 1].max(), points_torsion[:, 1].max())
168
+
169
+ # Création des DataFrames pour l'export
170
+ df_uniaxial = pd.DataFrame(points_uniaxial, columns=['Pression_hydrostatique', 'Cisaillement_max'])
171
+ df_torsion = pd.DataFrame(points_torsion, columns=['Pression_hydrostatique', 'Cisaillement_max'])
172
+
173
+ # Analyse du critère
174
+ alpha_est = 0.5
175
+ beta_est = points_uniaxial[:, 1].max() + alpha_est * points_uniaxial[:, 0].mean()
176
+
177
+ analysis_text = f"""
178
+ ### Analyse du critère de Dang Van
179
+
180
+ Le critère de Dang Van s'exprime sous la forme :
181
+
182
+ τ_a,max + α × p_h ≤ β
183
+
184
+ où :
185
+ - τ_a,max est l'amplitude maximale de cisaillement
186
+ - p_h est la pression hydrostatique
187
+ - α et β sont des constantes matériau
188
+
189
+ **Paramètres estimés :**
190
+ - α ≈ {alpha_est:.3f}
191
+ - β ≈ {beta_est:.1f} MPa
192
+ """
193
+
194
+ stats_text = f"""
195
+ ### 📊 Résultats statistiques
196
+
197
+ **Points uniaxiaux :** {stats_uniaxial} (σ₁={sigma1}MPa)
198
+ **Points torsion :** {stats_torsion} (ω={omega:.2f} rad/s)
199
+ **Pression hydro. moyenne :** {mean_hydro:.1f} MPa (Uniaxial)
200
+ **Cisaillement max :** {max_shear:.1f} MPa
201
+ """
202
+
203
+ return (
204
+ plot_url,
205
+ stats_text,
206
+ analysis_text,
207
+ df_uniaxial.head(20).to_html(classes='table table-striped'),
208
+ df_torsion.head(20).to_html(classes='table table-striped'),
209
+ df_uniaxial.to_csv(index=False),
210
+ df_torsion.to_csv(index=False),
211
+ buf.getvalue()
212
+ )
213
+
214
+ except Exception as e:
215
+ error_msg = f"Erreur lors du calcul : {str(e)}"
216
+ return None, error_msg, "", "", "", "", "", None
217
+
218
+ # Interface Gradio
219
+ with gr.Blocks(title="Critère de Dang Van - École Centrale Lyon") as demo:
220
+ gr.HTML(create_header())
221
+
222
+ with gr.Row():
223
+ with gr.Column(scale=1):
224
+ gr.Markdown("### 🔧 Paramètres d'étude")
225
+
226
+ sigma1 = gr.Slider(
227
+ minimum=10,
228
+ maximum=200,
229
+ value=100,
230
+ step=5,
231
+ label="Amplitude σ₁ (MPa)",
232
+ info="Amplitude de contrainte en traction-compression"
233
+ )
234
+
235
+ omega = gr.Slider(
236
+ minimum=0.1,
237
+ maximum=10.0,
238
+ value=2*pi,
239
+ step=0.1,
240
+ label="ω (rad/s)",
241
+ info="Fréquence angulaire du chargement"
242
+ )
243
+
244
+ fin = gr.Slider(
245
+ minimum=0.1,
246
+ maximum=2.0,
247
+ value=1.0,
248
+ step=0.1,
249
+ label="Temps final"
250
+ )
251
+
252
+ pasTemps = gr.Slider(
253
+ minimum=0.001,
254
+ maximum=0.1,
255
+ value=0.01,
256
+ step=0.001,
257
+ label="Pas de temps"
258
+ )
259
+
260
+ gr.Markdown("### 📊 Options d'affichage")
261
+ point_size = gr.Slider(10, 100, 30, label="Taille des points")
262
+ show_grid = gr.Checkbox(True, label="Afficher la grille")
263
+ theme = gr.Dropdown(
264
+ ["Classique", "Moderne", "Scientifique"],
265
+ value="Classique",
266
+ label="Thème du graphique"
267
+ )
268
+
269
+ calculate_btn = gr.Button(
270
+ "🚀 Lancer le calcul et la visualisation",
271
+ variant="primary",
272
+ size="lg"
273
+ )
274
+
275
+ with gr.Column(scale=2):
276
+ gr.Markdown("### Objectif de l'étude")
277
+ gr.HTML("""
278
+ <div class="centrale-card">
279
+ <p>Cette application permet d'analyser le comportement en fatigue des matériaux selon le critère de Dang Van.
280
+ Le critère permet de prédire l'apparition de fissures de fatigue en considérant simultanément la pression
281
+ hydrostatique et l'amplitude de cisaillement.</p>
282
+ </div>
283
+ """)
284
+
285
+ plot_output = gr.HTML()
286
+
287
+ with gr.Tabs():
288
+ with gr.TabItem("📈 Statistiques"):
289
+ stats_output = gr.Markdown()
290
+
291
+ with gr.TabItem("📄 Analyse"):
292
+ analysis_output = gr.Markdown()
293
+
294
+ with gr.TabItem("💾 Export des données"):
295
+ with gr.Row():
296
+ with gr.Column():
297
+ gr.Markdown("**Données Traction-Compression**")
298
+ uniaxial_table = gr.HTML()
299
+ with gr.Column():
300
+ gr.Markdown("**Données Torsion**")
301
+ torsion_table = gr.HTML()
302
+
303
+ with gr.Row():
304
+ uniaxial_csv = gr.File(label="CSV Traction-Compression")
305
+ torsion_csv = gr.File(label="CSV Torsion")
306
+ plot_download = gr.File(label="Graphique PNG")
307
+
308
+ # Informations
309
+ gr.HTML(create_info_panel())
310
+
311
+ # Footer
312
+ gr.HTML("""
313
+ <div class="footer">
314
+ <p><strong>École Centrale Lyon</strong> | Mécanique des Matériaux | UE: Fatigue et Fissuration</p>
315
+ <p style="font-size: 0.8rem; opacity: 0.8;">
316
+ Rapport technique - © 2024 - Tous droits réservés
317
+ </p>
318
+ </div>
319
+ """)
320
+
321
+ # Gestion des événements
322
+ calculate_btn.click(
323
+ fn=calculate_dang_van,
324
+ inputs=[sigma1, omega, fin, pasTemps, point_size, show_grid, theme],
325
+ outputs=[
326
+ plot_output,
327
+ stats_output,
328
+ analysis_output,
329
+ uniaxial_table,
330
+ torsion_table,
331
+ uniaxial_csv,
332
+ torsion_csv,
333
+ plot_download
334
+ ]
335
+ )
336
+
337
+ # Lancement de l'application
338
+ if __name__ == "__main__":
339
+ demo.launch(
340
+ server_name="0.0.0.0",
341
+ server_port=7860,
342
+ share=False,
343
+ css=css
344
+ )
app_gradio.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import versDV as dv
5
+ import deviatoire as dev
6
+ from math import pi
7
+ import pandas as pd
8
+ import io
9
+ import base64
10
+
11
+ # CSS personnalisé avec les couleurs de l'école
12
+ css = """
13
+ /* Palette de couleurs Centrale Lyon (version rouge) */
14
+ :root {
15
+ --primary-red: #D52B1E;
16
+ --secondary-red: #B22222;
17
+ --accent-red: #8B0000;
18
+ --light-gray: #F5F5F5;
19
+ --dark-gray: #333333;
20
+ }
21
+
22
+ .gradio-container {
23
+ font-family: 'Arial', sans-serif;
24
+ }
25
+
26
+ .main-header {
27
+ background: linear-gradient(90deg, var(--primary-red) 0%, var(--secondary-red) 100%);
28
+ padding: 1.5rem;
29
+ border-radius: 0 0 10px 10px;
30
+ color: white;
31
+ text-align: center;
32
+ margin-bottom: 2rem;
33
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
34
+ }
35
+
36
+ .centrale-title {
37
+ font-family: 'Georgia', serif;
38
+ font-weight: 700;
39
+ margin-bottom: 0.5rem;
40
+ }
41
+
42
+ .centrale-subtitle {
43
+ font-family: 'Arial', sans-serif;
44
+ font-size: 1.2rem;
45
+ opacity: 0.9;
46
+ }
47
+
48
+ .centrale-card {
49
+ border-left: 4px solid var(--accent-red);
50
+ padding: 1rem;
51
+ background-color: var(--light-gray);
52
+ border-radius: 0 8px 8px 0;
53
+ margin: 1rem 0;
54
+ }
55
+
56
+ .footer {
57
+ background-color: var(--dark-gray);
58
+ color: white;
59
+ padding: 1rem;
60
+ text-align: center;
61
+ margin-top: 2rem;
62
+ border-radius: 8px 8px 0 0;
63
+ font-size: 0.9rem;
64
+ }
65
+ """
66
+
67
+ def create_header():
68
+ """Crée le header HTML avec le style Centrale Lyon"""
69
+ return f"""
70
+ <div class="main-header">
71
+ <h1 class="centrale-title">ÉCOLE CENTRALE LYON</h1>
72
+ <h2 class="centrale-subtitle">Analyse de Fatigue - Critère de Dang Van</h2>
73
+ </div>
74
+ """
75
+
76
+ def create_info_panel():
77
+ """Crée le panneau d'informations"""
78
+ return """
79
+ <div class="centrale-card">
80
+ <h3>📝 À propos du critère de Dang Van</h3>
81
+ <p>Le critère de Dang Van est un critère multiaxial de fatigue à haute durée de vie.
82
+ Il permet de prendre en compte l'effet de la pression hydrostatique sur l'endurance
83
+ en fatigue des matériaux métalliques. Cette application visualise le domaine de
84
+ sécurité défini par ce critère pour différents types de chargements.</p>
85
+
86
+ <h4>👥 Équipe</h4>
87
+ <p><strong>Étudiants :</strong> Kevin TONGUE, Paul LORTHIOIR</p>
88
+ <p><strong>Enseignants :</strong> Éric FEULVACH, Françoise FAUVIN</p>
89
+ <p><strong>UE :</strong> Projet de recherche et innovation</p>
90
+ <p><strong>Thème :</strong> Analyse en fatigue de structures industrielles soumises à des chargements complexes</p>
91
+ <p><strong>Date :</strong> 2026</p>
92
+ </div>
93
+ """
94
+
95
+ def calculate_dang_van(sigma1, omega, fin, pasTemps, point_size, show_grid, theme):
96
+ """
97
+ Fonction principale de calcul pour le critère de Dang Van
98
+ """
99
+ try:
100
+ # Configuration du style selon le thème choisi
101
+ if theme == "Moderne":
102
+ plt.style.use('seaborn-v0_8-darkgrid')
103
+ elif theme == "Scientifique":
104
+ plt.style.use('seaborn-v0_8-paper')
105
+ else:
106
+ plt.style.use('default')
107
+
108
+ # Calcul des points pour chargement uniaxial
109
+ points_uniaxial = dv.nuage(sigma1, omega, pasTemps, fin)
110
+
111
+ # Calcul des points pour torsion
112
+ points_torsion = dv.nuageOrt(sigma1, omega, pasTemps, fin)
113
+
114
+ # Préparation de la figure
115
+ fig, ax = plt.subplots(figsize=(10, 6))
116
+
117
+ # Tracé des points
118
+ scatter1 = ax.scatter(
119
+ points_uniaxial[:, 0],
120
+ points_uniaxial[:, 1],
121
+ s=point_size,
122
+ alpha=0.7,
123
+ label='Traction-Compression',
124
+ edgecolors='white',
125
+ linewidth=1
126
+ )
127
+
128
+ scatter2 = ax.scatter(
129
+ points_torsion[:, 0],
130
+ points_torsion[:, 1],
131
+ s=point_size,
132
+ alpha=0.7,
133
+ label='Torsion',
134
+ edgecolors='white',
135
+ linewidth=1
136
+ )
137
+
138
+ # Configuration des axes et titres
139
+ ax.set_xlabel("Pression hydrostatique (MPa)", fontsize=12, fontweight='bold')
140
+ ax.set_ylabel("Amplitude de cisaillement max (MPa)", fontsize=12, fontweight='bold')
141
+ ax.set_title("Diagramme de Dang Van - École Centrale Lyon",
142
+ fontsize=14, fontweight='bold', pad=20)
143
+
144
+ if show_grid:
145
+ ax.grid(True, linestyle='--', alpha=0.3)
146
+
147
+ ax.legend(loc='best', frameon=True, fancybox=True, shadow=True)
148
+
149
+ # Ajustement des limites
150
+ xlim_min = min(points_uniaxial[:, 0].min(), points_torsion[:, 0].min()) - 10
151
+ xlim_max = max(points_uniaxial[:, 0].max(), points_torsion[:, 0].max()) + 10
152
+ ylim_max = max(points_uniaxial[:, 1].max(), points_torsion[:, 1].max()) + 10
153
+ ax.set_xlim(xlim_min, xlim_max)
154
+ ax.set_ylim(0, ylim_max)
155
+
156
+ # Sauvegarde de la figure
157
+ buf = io.BytesIO()
158
+ fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
159
+ buf.seek(0)
160
+ plot_url = f"data:image/png;base64,{base64.b64encode(buf.read()).decode()}"
161
+ plt.close(fig)
162
+
163
+ # Calcul des statistiques
164
+ stats_uniaxial = len(points_uniaxial)
165
+ stats_torsion = len(points_torsion)
166
+ mean_hydro = np.mean(points_uniaxial[:, 0])
167
+ max_shear = max(points_uniaxial[:, 1].max(), points_torsion[:, 1].max())
168
+
169
+ # Création des DataFrames pour l'export
170
+ df_uniaxial = pd.DataFrame(points_uniaxial, columns=['Pression_hydrostatique', 'Cisaillement_max'])
171
+ df_torsion = pd.DataFrame(points_torsion, columns=['Pression_hydrostatique', 'Cisaillement_max'])
172
+
173
+ # Analyse du critère
174
+ alpha_est = 0.5
175
+ beta_est = points_uniaxial[:, 1].max() + alpha_est * points_uniaxial[:, 0].mean()
176
+
177
+ analysis_text = f"""
178
+ ### Analyse du critère de Dang Van
179
+
180
+ Le critère de Dang Van s'exprime sous la forme :
181
+
182
+ τ_a,max + α × p_h ≤ β
183
+
184
+ où :
185
+ - τ_a,max est l'amplitude maximale de cisaillement
186
+ - p_h est la pression hydrostatique
187
+ - α et β sont des constantes matériau
188
+
189
+ **Paramètres estimés :**
190
+ - α ≈ {alpha_est:.3f}
191
+ - β ≈ {beta_est:.1f} MPa
192
+ """
193
+
194
+ stats_text = f"""
195
+ ### 📊 Résultats statistiques
196
+
197
+ **Points uniaxiaux :** {stats_uniaxial} (σ₁={sigma1}MPa)
198
+ **Points torsion :** {stats_torsion} (ω={omega:.2f} rad/s)
199
+ **Pression hydro. moyenne :** {mean_hydro:.1f} MPa (Uniaxial)
200
+ **Cisaillement max :** {max_shear:.1f} MPa
201
+ """
202
+
203
+ return (
204
+ plot_url,
205
+ stats_text,
206
+ analysis_text,
207
+ df_uniaxial.head(20).to_html(classes='table table-striped'),
208
+ df_torsion.head(20).to_html(classes='table table-striped'),
209
+ df_uniaxial.to_csv(index=False),
210
+ df_torsion.to_csv(index=False),
211
+ buf.getvalue()
212
+ )
213
+
214
+ except Exception as e:
215
+ error_msg = f"Erreur lors du calcul : {str(e)}"
216
+ return None, error_msg, "", "", "", "", "", None
217
+
218
+ # Interface Gradio
219
+ with gr.Blocks(title="Critère de Dang Van - École Centrale Lyon") as demo:
220
+ gr.HTML(create_header())
221
+
222
+ with gr.Row():
223
+ with gr.Column(scale=1):
224
+ gr.Markdown("### 🔧 Paramètres d'étude")
225
+
226
+ sigma1 = gr.Slider(
227
+ minimum=10,
228
+ maximum=200,
229
+ value=100,
230
+ step=5,
231
+ label="Amplitude σ₁ (MPa)",
232
+ info="Amplitude de contrainte en traction-compression"
233
+ )
234
+
235
+ omega = gr.Slider(
236
+ minimum=0.1,
237
+ maximum=10.0,
238
+ value=2*pi,
239
+ step=0.1,
240
+ label="ω (rad/s)",
241
+ info="Fréquence angulaire du chargement"
242
+ )
243
+
244
+ fin = gr.Slider(
245
+ minimum=0.1,
246
+ maximum=2.0,
247
+ value=1.0,
248
+ step=0.1,
249
+ label="Temps final"
250
+ )
251
+
252
+ pasTemps = gr.Slider(
253
+ minimum=0.001,
254
+ maximum=0.1,
255
+ value=0.01,
256
+ step=0.001,
257
+ label="Pas de temps"
258
+ )
259
+
260
+ gr.Markdown("### 📊 Options d'affichage")
261
+ point_size = gr.Slider(10, 100, 30, label="Taille des points")
262
+ show_grid = gr.Checkbox(True, label="Afficher la grille")
263
+ theme = gr.Dropdown(
264
+ ["Classique", "Moderne", "Scientifique"],
265
+ value="Classique",
266
+ label="Thème du graphique"
267
+ )
268
+
269
+ calculate_btn = gr.Button(
270
+ "🚀 Lancer le calcul et la visualisation",
271
+ variant="primary",
272
+ size="lg"
273
+ )
274
+
275
+ with gr.Column(scale=2):
276
+ gr.Markdown("### Objectif de l'étude")
277
+ gr.HTML("""
278
+ <div class="centrale-card">
279
+ <p>Cette application permet d'analyser le comportement en fatigue des matériaux selon le critère de Dang Van.
280
+ Le critère permet de prédire l'apparition de fissures de fatigue en considérant simultanément la pression
281
+ hydrostatique et l'amplitude de cisaillement.</p>
282
+ </div>
283
+ """)
284
+
285
+ plot_output = gr.HTML()
286
+
287
+ with gr.Tabs():
288
+ with gr.TabItem("📈 Statistiques"):
289
+ stats_output = gr.Markdown()
290
+
291
+ with gr.TabItem("📄 Analyse"):
292
+ analysis_output = gr.Markdown()
293
+
294
+ with gr.TabItem("💾 Export des données"):
295
+ with gr.Row():
296
+ with gr.Column():
297
+ gr.Markdown("**Données Traction-Compression**")
298
+ uniaxial_table = gr.HTML()
299
+ with gr.Column():
300
+ gr.Markdown("**Données Torsion**")
301
+ torsion_table = gr.HTML()
302
+
303
+ with gr.Row():
304
+ uniaxial_csv = gr.File(label="CSV Traction-Compression")
305
+ torsion_csv = gr.File(label="CSV Torsion")
306
+ plot_download = gr.File(label="Graphique PNG")
307
+
308
+ # Informations
309
+ gr.HTML(create_info_panel())
310
+
311
+ # Footer
312
+ gr.HTML("""
313
+ <div class="footer">
314
+ <p><strong>École Centrale Lyon</strong> | Mécanique des Matériaux | UE: Fatigue et Fissuration</p>
315
+ <p style="font-size: 0.8rem; opacity: 0.8;">
316
+ Rapport technique - © 2024 - Tous droits réservés
317
+ </p>
318
+ </div>
319
+ """)
320
+
321
+ # Gestion des événements
322
+ calculate_btn.click(
323
+ fn=calculate_dang_van,
324
+ inputs=[sigma1, omega, fin, pasTemps, point_size, show_grid, theme],
325
+ outputs=[
326
+ plot_output,
327
+ stats_output,
328
+ analysis_output,
329
+ uniaxial_table,
330
+ torsion_table,
331
+ uniaxial_csv,
332
+ torsion_csv,
333
+ plot_download
334
+ ]
335
+ )
336
+
337
+ # Lancement de l'application
338
+ if __name__ == "__main__":
339
+ demo.launch(
340
+ server_name="0.0.0.0",
341
+ server_port=7860,
342
+ share=False,
343
+ css=css
344
+ )
deviatoire.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from math import *
3
+ import versDV as dv
4
+ # def deviateur(tens):
5
+
6
+ # p = dv.hydro(tens)
7
+ # res = []
8
+ # for i in range(3):
9
+ # res.append(tens[i]-p)
10
+ # for i in range(3,6):
11
+ # res.append(tens[i])
12
+ # return np.array(res)
13
+
14
+
15
+ def deviateur(tens):
16
+ """
17
+ cette fonction se base sur les fonctions initialement écrites dans versDV.py pour calculer le tenseur déviateur
18
+ 1. on convertit le tenseur en matrice 3x3
19
+ 2. on calcule la pression hydrostatique
20
+ 3. on soustrait la pression hydrostatique à la matrice
21
+ 4. on reconvertit la matrice déviateur en tenseur
22
+ 5. on retourne le tenseur déviateur
23
+ """
24
+ dev=dv.tens_to_mat(tens)-np.eye(3)*dv.hydro(tens)
25
+ return dv.mat_to_tens(dev)
26
+
27
+ # def hydro(tens):
28
+ # p = 0
29
+ # for i in range(3):
30
+ # p = p + tens[i]
31
+ # return p/3
32
+
33
+ # def genereTens(sigma1,omega,pasTemps,fin):
34
+ # tens = np.array([sigma1,0,0,0,0,0])
35
+ # for i in range(int(fin/pasTemps)):
36
+ # t = (i+1)*pasTemps
37
+ # ligne = np.array([sigma1*cos(omega*t),0,0,0,0,0])
38
+ # tens = np.vstack((tens, ligne))
39
+ # # omega est la pulsation, vous pouvez choisir 2*pi par exemple
40
+ # # sigma1 est fixe, par exemple 100 MPa
41
+ # # cette fonction doit générer une matrice de 6 colonnes, chaque ligne étant le tenseur à un instant du cycle, et de la forme [sigma1*cos(omega*t),0,0,0,0,0]
42
+ # return tens
43
+
44
+ # def CalculMatDev(matTens):
45
+ # resDev = matTens[:,[0,1,3,4,5]]
46
+ # taille = resDev.shape
47
+ # nLig = taille[0]
48
+ # for i in range(nLig):
49
+ # pH = hydro(matTens[i])
50
+ # for j in range(3):
51
+ # resDev[i][j] = resDev[i][j]-pH
52
+ # return resDev
53
+ def CalculMatDev(matTens):
54
+ return np.array([deviateur(matTens[i]) for i in range(matTens.shape[0])])
55
+ def normeTresca(tens):
56
+ TensM=dv.tens_to_mat(tens)
57
+ valP = np.linalg.eigvals(TensM)
58
+ max = float(np.max(valP))
59
+ min = float(np.min(valP))
60
+ return abs(max-min)
61
+
62
+ def diametre(matTens):
63
+ """calcul la distance maximale entre deux lignes (des déviateurs) au sens de la norme de Tresca) et retourne les deux points extrêmes, """
64
+
65
+ matDev=CalculMatDev(matTens)
66
+ point1=np.zeros(5)
67
+ point2=np.zeros(5)
68
+ maxDist=0
69
+ for i in range(matDev.shape[0]-1):
70
+ point1 = matDev[i]
71
+ for j in range(i+1,matDev.shape[0]):
72
+ point2 = matDev[j]
73
+ dist=normeTresca(matDev[i]-matDev[j])
74
+ if dist>maxDist:
75
+ maxDist=dist
76
+ point1=matDev[i]
77
+ point2=matDev[j]
78
+ return maxDist, point1, point2
79
+
80
+
81
+ def recentre(matDev):
82
+ """retourne une matrice de tenseurs déviateurs recentrée par Centre qui est un tenseur."""
83
+ # taille = matDev.shape
84
+ # nLig = taille[0]
85
+ # for i in range(nLig):
86
+ # ligne = matDev[i] - Centre
87
+ # matCentre = np.vstack((matCentre, ligne))
88
+ points = diametre(matDev)
89
+ Centre = (points[1] + points[2])/2
90
+ return matDev - Centre
91
+
92
+ # class Deviateur:
93
+
94
+
95
+
96
+ # matTens = dv.genereTens(100,2*pi,0.1,1)
97
+ # res = CalculMatDev(matTens)
98
+
99
+ # trace le nuage de points J2(matDev)(t),p(t))
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ numpy
2
+ matplotlib
3
+ scipy
4
+ gradio
5
+ pandas
versDV.py ADDED
@@ -0,0 +1,342 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cellule 1 : Installation des packages (optionnel)
2
+ # !pip install numpy matplotlib
3
+
4
+ # Cellule 2 : Import des bibliothèques
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ from math import *
8
+ import cmath
9
+ import scipy.special as sp
10
+
11
+ # Cellule 3 : Fonction normale
12
+ def normale(theta,phi):
13
+ # retourne le vecteur unitaire définit par (cos(theta)*sin(phi),sin(theta)*sin(phi),cos(phi))
14
+ vN = np.array([cos(theta)*sin(phi),sin(theta)*sin(phi),cos(phi)])
15
+ return vN.T
16
+
17
+ # Cellule 4 : Fonction tens_to_mat
18
+ def tens_to_mat(liste):
19
+ if isinstance(liste,list):
20
+ liste = np.array(liste)
21
+ res = np.array([[liste[0],liste[3],liste[4]],
22
+ [liste[3],liste[1],liste[5]],
23
+ [liste[4],liste[5],liste[2]]])
24
+ return res
25
+
26
+ # Cellule 5 : Fonction contTang
27
+ def mat_to_tens(mat):
28
+ res = np.array([mat[0,0],mat[1,1],mat[2,2],mat[0,1],mat[0,2],mat[1,2]])
29
+ return res
30
+ def contTang(tens,vN):
31
+ # calcul le vecteur contrainte tangentiell sur une facette de normale vN
32
+ M = tens_to_mat(tens) # vecteur contrainte
33
+ cont = M@vN # contrainte normale
34
+ cN = cont@vN
35
+ contT = cont-cN*vN
36
+ return contT
37
+
38
+ # Cellule 6 : Fonction hydro
39
+ def hydro(tens):
40
+ # cette fonction doit retourner la pression hydrostatique associée à ce tenseur (c'est pour un instant du cycle !)
41
+ if isinstance(tens, np.ndarray):
42
+ if tens.ndim == 2 and tens.shape == (3, 3):
43
+ return np.trace(tens) / 3
44
+ elif tens.ndim == 1:
45
+ if len(tens) == 6:
46
+ return (tens[0] + tens[1] + tens[2]) / 3
47
+ elif len(tens) == 3:
48
+ return np.sum(tens) / 3
49
+ else:
50
+ raise ValueError("Vector must be of length 3 or 6")
51
+ elif tens.ndim == 2:
52
+ # assume array of vectors
53
+ if tens.shape[1] not in [3, 6]:
54
+ raise ValueError("Each row must be length 3 or 6")
55
+ res = []
56
+ for i in range(tens.shape[0]):
57
+ row = tens[i]
58
+ if tens.shape[1] == 6:
59
+ p = (row[0] + row[1] + row[2]) / 3
60
+ else: # 3
61
+ p = np.sum(row) / 3
62
+ res.append(p)
63
+ return np.array(res)
64
+ else:
65
+ raise ValueError("Unsupported tensor shape")
66
+ else:
67
+ # if list, convert
68
+ tens = np.array(tens)
69
+ return hydro(tens)
70
+
71
+ # Cellule 7 : Fonction genereTens
72
+ def genereTens(sigma1,omega,pasTemps,fin):
73
+ tens = np.array([sigma1,0,0,0,0,0])
74
+ for i in range(int(fin/pasTemps)):
75
+ t = (i+1)*pasTemps
76
+ ligne = np.array([sigma1*cos(omega*t),0,0,0,0,0])
77
+ tens = np.vstack((tens, ligne))
78
+ # omega est la pulsation, vous pouvez choisir 2*pi par exemple
79
+ # sigma1 est fixe, par exemple 100 MPa
80
+ # cette fonction doit générer une matrice de 6 colonnes, chaque ligne étant le tenseur à un instant du cycle, et de la forme [sigma1*cos(omega*t),0,0,0,0,0]
81
+ return tens
82
+ def genereTensOrt(sigma1,omega,pasTemps,fin):
83
+ tens = np.array([0,0,0,sigma1,0,0])
84
+ for i in range(int(fin/pasTemps)):
85
+ t = (i+1)*pasTemps
86
+ ligne = np.array([0,0,0,sigma1*cos(omega*t),0,0])
87
+ tens = np.vstack((tens, ligne))
88
+ # omega est la pulsation, vous pouvez choisir 2*pi par exemple
89
+ # sigma1 est fixe, par exemple 100 MPa
90
+ # cette fonction doit générer une matrice de 6 colonnes, chaque ligne étant le tenseur à un instant du cycle, et de la forme [sigma1*cos(omega*t),0,0,0,0,0]
91
+ return tens
92
+
93
+ # Cellule 8 : Test de genereTens
94
+ # genereTens(100,2*pi,0.01,1)
95
+
96
+ # Cellule 9 : Fonction amplitudeTangMax
97
+ def amplitudeTangMax(tens):
98
+ # cette fonction doit retourner pour UN instant une liste de deux éléments :
99
+ # le premier élément est la valeur max_n (norme de contTang) et le deuxième les angles du plan associés
100
+ # il faut balayer les facettes !
101
+ maxi = 0
102
+ theta = 0
103
+ planMax = [0,0]
104
+ phi = 0
105
+ pasTheta = pi/180
106
+ pasPhi = pi/180
107
+ vect_norm = normale(theta,phi)
108
+
109
+ for i in range(180+1):
110
+ theta = i*pasTheta
111
+ for j in range(180+1):
112
+ phi = j*pasPhi
113
+ # on construit le vecteur normal
114
+ vect_norm = normale(theta,phi)
115
+ # on calcule la contrainte tangentielle
116
+ contT = contTang(tens,vect_norm)
117
+ # on calcule sa norme
118
+ norme = np.linalg.norm(contT)
119
+ # si elle est plus grande que maxi, elle devient maxi
120
+ if norme > maxi:
121
+ maxi = norme
122
+ planMax = [theta,phi]
123
+ # on actualise planMax
124
+ # on retourne [maxi,planMax]
125
+ return [maxi,planMax]
126
+
127
+ # Cellule 10 : Fonction nuage
128
+ def nuage(sigma1,omega,pasTemps,fin):
129
+ """
130
+ Le but de la fonction est de tracer les contraintes tangentielles maximales en fonction de la
131
+ pression hydrostatique
132
+ """
133
+ points = np.array([0,0])
134
+ tensTot = genereTens(sigma1,omega,pasTemps,fin)
135
+ for t in range(int(fin/pasTemps)+1):
136
+ tens = tensTot[t]
137
+ cisMax,_ = amplitudeTangMax(tens)
138
+ hydros = hydro(tens)
139
+ ligne = np.array([hydros,cisMax])
140
+ points = np.vstack((points, ligne))
141
+ return points
142
+
143
+
144
+
145
+ def nuageOrt(sigma1,omega,pasTemps,fin):
146
+ """
147
+ Le but de la fonction est de tracer les contraintes tangentielles maximales en fonction de la
148
+ pression hydrostatique
149
+ """
150
+ points = np.array([0,0])
151
+ tensTot = genereTensOrt(sigma1,omega,pasTemps,fin)
152
+ for t in range(int(fin/pasTemps)+1):
153
+ tens = tensTot[t]
154
+ cisMax,_ = amplitudeTangMax(tens)
155
+ hydros = hydro(tens)
156
+ ligne = np.array([hydros,cisMax])
157
+ points = np.vstack((points, ligne))
158
+ return points
159
+
160
+ # Cellule 11 : Fonction traceNuage
161
+ def traceNuage(points):
162
+ plt.scatter(points[:, 0], points[:, 1])
163
+ plt.xlabel("pression hydrostatique")
164
+ plt.ylabel("amplitude de cisaillement max")
165
+ plt.title("Nuage de points")
166
+ plt.savefig('dangvan_nuage.png')
167
+ plt.show()
168
+
169
+ # Cellule 12 : Exécution et visualisation
170
+ # points = nuage(100,2*pi,0.01,1)
171
+ # traceNuage(points)
172
+
173
+ # # Tracer le diagramme de Dang Van avec les deux cas et la limite
174
+ # points_uniaxial = nuage(100, 2*pi, 0.01, 1)
175
+ # points_torsion = nuageOrt(50, 2*pi, 0.01, 1) # tau_a = 50 for torsion
176
+
177
+ # plt.figure()
178
+ # plt.scatter(points_uniaxial[:, 0], points_uniaxial[:, 1], label='Traction-Compression')
179
+ # plt.scatter(points_torsion[:, 0], points_torsion[:, 1], label='Torsion')
180
+
181
+ # # Limite de fatigue : ligne droite reliant les maxima
182
+ # max_uniaxial = np.max(points_uniaxial[:, 1])
183
+ # max_torsion = np.max(points_torsion[:, 1])
184
+
185
+ # # Pour Dang Van, la ligne limite est tau = beta - alpha * p
186
+ # # Ici, simplifié : ligne de (0, max_torsion) à (max_p_uniaxial, 0) ou quelque chose
187
+ # # En pratique, alpha ≈ 0.3, beta ≈ max_torsion
188
+ # alpha = 0.3
189
+ # beta = max_torsion
190
+ # p_max = np.max(points_uniaxial[:, 0])
191
+ # p_line = np.linspace(0, p_max, 100)
192
+ # tau_line = beta - alpha * p_line
193
+ # plt.plot(p_line, tau_line, 'r-', label='Limite de fatigue')
194
+
195
+ # plt.xlabel("Pression hydrostatique")
196
+ # plt.ylabel("Amplitude de cisaillement max")
197
+ # plt.title("Diagramme de Dang Van")
198
+ # plt.legend()
199
+ # plt.grid(True)
200
+ # plt.savefig('dangvan_limite.png')
201
+ # plt.show()
202
+
203
+
204
+
205
+ class DangVan:
206
+ @staticmethod
207
+ def normale(theta, phi):
208
+ # retourne le vecteur unitaire définit par (cos(theta)*sin(phi),sin(theta)*sin(phi),cos(phi))
209
+ vN = np.array([cos(theta)*sin(phi), sin(theta)*sin(phi), cos(phi)])
210
+ return vN.T
211
+
212
+ @staticmethod
213
+ def tens_to_mat(liste):
214
+ if isinstance(liste, list):
215
+ liste = np.array(liste)
216
+ res = np.array([[liste[0], liste[3], liste[4]],
217
+ [liste[3], liste[1], liste[5]],
218
+ [liste[4], liste[5], liste[2]]])
219
+ return res
220
+
221
+ @staticmethod
222
+ def contTang(tens, vN):
223
+ # calcul le vecteur contrainte tangentiell sur une facette de normale vN
224
+ M = DangVan.tens_to_mat(tens) # vecteur contrainte
225
+ cont = M @ vN # contrainte normale
226
+ cN = cont @ vN
227
+ contT = cont - cN * vN
228
+ return contT
229
+
230
+ @staticmethod
231
+ def hydro(tens):
232
+ # cette fonction doit retourner la pression hydrostatique associée à ce tenseur (c'est pour un instant du cycle !)
233
+ if isinstance(tens, np.ndarray):
234
+ if tens.ndim == 2 and tens.shape == (3, 3):
235
+ return np.trace(tens) / 3
236
+ elif tens.ndim == 1:
237
+ if len(tens) == 6:
238
+ return (tens[0] + tens[1] + tens[2]) / 3
239
+ elif len(tens) == 3:
240
+ return np.sum(tens) / 3
241
+ else:
242
+ raise ValueError("Vector must be of length 3 or 6")
243
+ else:
244
+ raise ValueError("Unsupported tensor shape for single tensor")
245
+ else:
246
+ tens = np.array(tens)
247
+ return DangVan.hydro(tens)
248
+
249
+ @staticmethod
250
+ def genereTens(sigma1, omega, pasTemps, fin):
251
+ tens = np.array([sigma1, 0, 0, 0, 0, 0])
252
+ for i in range(int(fin / pasTemps)):
253
+ t = (i + 1) * pasTemps
254
+ ligne = np.array([sigma1 * cos(omega * t), 0, 0, 0, 0, 0])
255
+ tens = np.vstack((tens, ligne))
256
+ # omega est la pulsation, vous pouvez choisir 2*pi par exemple
257
+ # sigma1 est fixe, par exemple 100 MPa
258
+ # cette fonction doit générer une matrice de 6 colonnes, chaque ligne étant le tenseur à un instant du cycle, et de la forme [sigma1*cos(omega*t),0,0,0,0,0]
259
+ return tens
260
+
261
+ @staticmethod
262
+ def genereTensOrt(sigma1, omega, pasTemps, fin):
263
+ tens = np.array([0, 0, 0, sigma1, 0, 0])
264
+ for i in range(int(fin / pasTemps)):
265
+ t = (i + 1) * pasTemps
266
+ ligne = np.array([0, 0, 0, sigma1 * cos(omega * t), 0, 0])
267
+ tens = np.vstack((tens, ligne))
268
+ # omega est la pulsation, vous pouvez choisir 2*pi par exemple
269
+ # sigma1 est fixe, par exemple 100 MPa
270
+ # cette fonction doit générer une matrice de 6 colonnes, chaque ligne étant le tenseur à un instant du cycle, et de la forme [sigma1*cos(omega*t),0,0,0,0,0]
271
+ return tens
272
+
273
+ @staticmethod
274
+ def amplitudeTangMax(tens):
275
+ # cette fonction doit retourner pour UN instant une liste de deux éléments :
276
+ # le premier élément est la valeur max_n (norme de contTang) et le deuxième les angles du plan associés
277
+ # il faut balayer les facettes !
278
+ maxi = 0
279
+ theta = 0
280
+ planMax = [0, 0]
281
+ phi = 0
282
+ pasTheta = pi / 180
283
+ pasPhi = pi / 180
284
+ vect_norm = DangVan.normale(theta, phi)
285
+
286
+ for i in range(180 + 1):
287
+ theta = i * pasTheta
288
+ for j in range(180 + 1):
289
+ phi = j * pasPhi
290
+ # on construit le vecteur normal
291
+ vect_norm = DangVan.normale(theta, phi)
292
+ # on calcule la contrainte tangentielle
293
+ contT = DangVan.contTang(tens, vect_norm)
294
+ # on calcule sa norme
295
+ norme = np.linalg.norm(contT)
296
+ # si elle est plus grande que maxi, elle devient maxi
297
+ if norme > maxi:
298
+ maxi = norme
299
+ planMax = [theta, phi]
300
+ # on actualise planMax
301
+ # on retourne [maxi,planMax]
302
+ return [maxi, planMax]
303
+
304
+ @staticmethod
305
+ def nuage(sigma1, omega, pasTemps, fin):
306
+ """
307
+ Le but de la fonction est de tracer les contraintes tangentielles maximales en fonction de la
308
+ pression hydrostatique
309
+ """
310
+ points = np.array([0, 0])
311
+ tensTot = DangVan.genereTens(sigma1, omega, pasTemps, fin)
312
+ for t in range(int(fin / pasTemps)+1):
313
+ tens = tensTot[t]
314
+ cisMax, _ = DangVan.amplitudeTangMax(tens)
315
+ hydros = DangVan.hydro(tens)
316
+ ligne = np.array([hydros, cisMax])
317
+ points = np.vstack((points, ligne))
318
+ return points
319
+
320
+ @staticmethod
321
+ def nuageOrt(sigma1, omega, pasTemps, fin):
322
+ """
323
+ Le but de la fonction est de tracer les contraintes tangentielles maximales en fonction de la
324
+ pression hydrostatique
325
+ """
326
+ points = np.array([0, 0])
327
+ tensTot = DangVan.genereTensOrt(sigma1, omega, pasTemps, fin)
328
+ for t in range(int(fin / pasTemps)+1):
329
+ tens = tensTot[t]
330
+ cisMax, _ = DangVan.amplitudeTangMax(tens)
331
+ hydros = DangVan.hydro(tens)
332
+ ligne = np.array([hydros, cisMax])
333
+ points = np.vstack((points, ligne))
334
+ return points
335
+
336
+ @staticmethod
337
+ def traceNuage(points):
338
+ plt.scatter(points[:, 0], points[:, 1])
339
+ plt.xlabel("pression hydrostatique")
340
+ plt.ylabel("amplitude de cisaillement max")
341
+ plt.title("Nuage de points")
342
+ plt.show()