YAYA20 commited on
Commit
90865b9
·
verified ·
1 Parent(s): 5b6d9e5

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +467 -0
app.py ADDED
@@ -0,0 +1,467 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from scipy.integrate import odeint
3
+ import emcee
4
+ import panel as pn
5
+ import param
6
+ import plotly.graph_objects as go
7
+ from plotly.subplots import make_subplots
8
+ import plotly.io as pio
9
+
10
+
11
+ # Définition du thème sombre
12
+ pio.templates.default = "plotly_dark"
13
+
14
+ # Initialiser l'extension Panel
15
+ pn.extension('katex', 'mathjax', 'plotly')
16
+
17
+ # Informations personnelles
18
+ photo_url = "PPFB.png"
19
+ prenom = "Yaya"
20
+ nom = "Toure"
21
+ email = "[email protected]"
22
+ whatsapps_url = "https://wa.me/message/GW7RWRW3GR4WN1"
23
+ linkedin_url = "https://www.linkedin.com/in/yaya-toure-8251a4280/"
24
+ github_url = "https://github.com/CodingYayaToure"
25
+ universite = "UNCHK"
26
+ formation = "Licence Analyse Numerique et Modelisation | Master Calcul scientifique et Modelisation"
27
+ certificat = "Collecte et Fouille de Donnees (UADB-CNAM Paris)"
28
+ TP_Scilab = "https://codingyayatoure.github.io/Equation_de_Transport_en_1D_par_la_Methode_des_Differences_Finis/"
29
+
30
+ # Créer le template du tableau de bord
31
+ template = pn.template.ReactTemplate(title='Progiciel de Simulation des Interactions Microbiennes en Fermentation de Jus de Légumes. | Approche Modélisation mathématique ')
32
+
33
+ # Classe pour la dynamique Consumer-Resource
34
+ class ConsumerResourceApp(param.Parameterized):
35
+ # Paramètres du modèle
36
+ r = param.Number(10.0, bounds=(0.1, 20.0), step=0.1, doc="Taux d'approvisionnement en ressources")
37
+ d = param.Number(0.1, bounds=(0.01, 1.0), step=0.01, doc="Taux de mortalité des consommateurs")
38
+ c = param.Number(0.1, bounds=(0.01, 1.0), step=0.01, doc="Coefficient de consommation")
39
+ K = param.Number(1.0, bounds=(0.1, 10.0), step=0.1, doc="Concentration à laquelle la croissance est à moitié maximale")
40
+ X0 = param.Number(1.0, bounds=(0.1, 10.0), step=0.1, doc="Population initiale des consommateurs")
41
+ S0 = param.Number(5.0, bounds=(0.1, 20.0), step=0.1, doc="Concentration initiale de la ressource")
42
+
43
+ @param.depends('r', 'd', 'c', 'K', 'X0', 'S0')
44
+ def view(self):
45
+ # Fonction de croissance dépendante de la ressource (Monod)
46
+ def mu(S):
47
+ return S / (self.K + S)
48
+
49
+ # Équations différentielles
50
+ def model(y, t):
51
+ X, S = y
52
+ dXdt = mu(S) * X - self.d * X # Dynamique des consommateurs
53
+ dSdt = self.r - self.c * mu(S) * X # Dynamique des ressources
54
+ return [dXdt, dSdt]
55
+
56
+ # Conditions initiales
57
+ y0 = [self.X0, self.S0]
58
+
59
+ # Temps pour la simulation (en heures)
60
+ t_hours = np.linspace(0, 100, 500)
61
+
62
+ # Résoudre le système d'équations différentielles
63
+ solution = odeint(model, y0, t_hours)
64
+
65
+ # Créer les graphiques avec Plotly en utilisant des sous-graphiques
66
+ fig = make_subplots(
67
+ rows=1, cols=3,
68
+ specs=[[{'type': 'scatter'}, {'type': 'scatter'}, {'type': 'scatter'}]],
69
+ subplot_titles=["Population des Consommateurs (X)", "Concentration de la Ressource (S)", "Population et Ressource sur le même repère"]
70
+ )
71
+
72
+ # Ajouter la trace pour les consommateurs
73
+ fig.add_trace(go.Scatter(x=t_hours, y=solution[:, 0], mode='lines', name='Population des Consommateurs (X)', line=dict(color='blue')), row=1, col=1)
74
+
75
+ # Ajouter la trace pour les ressources
76
+ fig.add_trace(go.Scatter(x=t_hours, y=solution[:, 1], mode='lines', name='Concentration de la Ressource (S)', line=dict(color='green')), row=1, col=2)
77
+
78
+ # Ajouter la trace pour les consommateurs et les ressources sur le même graphique
79
+ fig.add_trace(go.Scatter(x=t_hours, y=solution[:, 0], mode='lines', name='Population des Consommateurs (X)', line=dict(color='blue')), row=1, col=3)
80
+ fig.add_trace(go.Scatter(x=t_hours, y=solution[:, 1], mode='lines', name='Concentration de la Ressource (S)', line=dict(color='green')), row=1, col=3)
81
+
82
+ # Mettre à jour la mise en page du graphique
83
+ fig.update_layout(
84
+ title='Dynamique du Modèle Consumer-Resource',
85
+ xaxis_title='Temps',
86
+ yaxis_title='Population / Concentration',
87
+ legend_title='Légende',
88
+ height=400,
89
+ width=1500,
90
+ font_color="white",
91
+ template='plotly_dark'
92
+ )
93
+
94
+ return pn.pane.Plotly(fig)
95
+
96
+ def panel_view(self):
97
+ return pn.Column(
98
+ pn.Row(pn.Param(self, width=400), self.formulas()), # Aligner les widgets et les formules sur la même ligne
99
+ self.view # Appeler la vue pour qu'elle se mette à jour dynamiquement
100
+ )
101
+
102
+ def formulas(self):
103
+ return pn.Column(
104
+ pn.pane.LaTeX(r"""
105
+ $$
106
+ \text{Fonction de croissance : } \mu(S) = \frac{S}{K + S}
107
+ $$
108
+ """, styles={'font-size': '16pt'}),
109
+ pn.pane.LaTeX(r"""
110
+ $$
111
+ \text{Dynamique des consommateurs : } \frac{dX}{dt} = \mu(S) \times X - d \times X
112
+ $$
113
+ """, styles={'font-size': '16pt'}),
114
+ pn.pane.LaTeX(r"""
115
+ $$
116
+ \text{Dynamique des ressources : } \frac{dS}{dt} = r - c \times \mu(S) \times X
117
+ $$
118
+ """, styles={'font-size': '16pt'})
119
+ )
120
+
121
+ # Classe pour la dynamique des populations microbiennes
122
+ class MicrobialDynamicsApp(param.Parameterized):
123
+ # Paramètres du modèle pour la dynamique des populations microbiennes
124
+ r1 = param.Number(0.5, bounds=(0.1, 1.0), step=0.01, doc="Taux de croissance espèce 1")
125
+ r2 = param.Number(0.3, bounds=(0.1, 1.0), step=0.01, doc="Taux de croissance espèce 2")
126
+ K1 = param.Number(100, bounds=(10, 200), step=1, doc="Capacité de charge espèce 1")
127
+ K2 = param.Number(80, bounds=(10, 200), step=1, doc="Capacité de charge espèce 2")
128
+ alpha = param.Number(0.01, bounds=(0.001, 0.1), step=0.001, doc="Compétition espèce 2 sur espèce 1")
129
+ beta = param.Number(0.01, bounds=(0.001, 0.1), step=0.001, doc="Compétition espèce 1 sur espèce 2")
130
+
131
+ @param.depends('r1', 'r2', 'K1', 'K2', 'alpha', 'beta')
132
+ def view(self):
133
+ # Équations différentielles pour les populations microbiennes
134
+ def model(y, t):
135
+ X1, X2 = y
136
+ dX1dt = self.r1 * X1 * (1 - (X1 + self.alpha * X2) / self.K1)
137
+ dX2dt = self.r2 * X2 * (1 - (X2 + self.beta * X1) / self.K2)
138
+ return [dX1dt, dX2dt]
139
+
140
+ # Conditions initiales
141
+ y0 = [10, 5] # Population initiale des deux espèces
142
+
143
+ # Temps pour la simulation
144
+ t = np.linspace(0, 50, 500)
145
+
146
+ # Résoudre le système d'équations différentielles
147
+ solution = odeint(model, y0, t)
148
+
149
+ # Créer le graphique avec Plotly
150
+ fig = go.Figure()
151
+ fig.add_trace(go.Scatter(x=t, y=solution[:, 0], mode='lines', name='Espèce 1', line=dict(color='red')))
152
+ fig.add_trace(go.Scatter(x=t, y=solution[:, 1], mode='lines', name='Espèce 2', line=dict(color='purple')))
153
+
154
+ # Mettre à jour la mise en page du graphique
155
+ fig.update_layout(
156
+ title='Dynamique des Populations Microbiennes',
157
+ xaxis_title='Temps',
158
+ yaxis_title='Population',
159
+ legend_title='Légende',
160
+ height=400,
161
+ width=1500,
162
+ font_color="white",
163
+ template='plotly_dark'
164
+ )
165
+
166
+ return pn.pane.Plotly(fig)
167
+
168
+ def panel_view(self):
169
+ return pn.Column(
170
+ pn.Row(pn.Param(self, width=400), self.formulas()), # Aligner les widgets et les formules sur la même ligne
171
+ self.view # Appeler la vue pour qu'elle se mette à jour dynamiquement
172
+ )
173
+
174
+ def formulas(self):
175
+ return pn.Column(
176
+ pn.pane.LaTeX(r"""
177
+ $$
178
+ \frac{{dX_1}}{{dt}} = r_1 X_1 \left( 1 - \frac{{X_1 + \alpha X_2}}{{K_1}} \right)
179
+ $$
180
+ """, styles={'font-size': '16pt'}),
181
+ pn.pane.LaTeX(r"""
182
+ $$
183
+ \frac{{dX_2}}{{dt}} = r_2 X_2 \left( 1 - \frac{{X_2 + \beta X_1}}{{K_2}} \right)
184
+ $$
185
+ """, styles={'font-size': '16pt'})
186
+ )
187
+
188
+ # Classe pour la modélisation de la fermentation
189
+ class FermentationModelApp(param.Parameterized):
190
+ # Paramètres du modèle de fermentation
191
+ mu_max = param.Number(0.8, bounds=(0.1, 2.0), step=0.1, doc="Taux de croissance maximal")
192
+ Ks = param.Number(10.0, bounds=(1.0, 50.0), step=1.0, doc="Constante de demi-saturation")
193
+ Yxs = param.Number(0.5, bounds=(0.1, 1.0), step=0.1, doc="Rendement de croissance sur substrat")
194
+ Sin = param.Number(20.0, bounds=(10.0, 100.0), step=1.0, doc="Concentration initiale en substrat")
195
+ X0 = param.Number(0.1, bounds=(0.01, 5.0), step=0.01, doc="Concentration initiale en biomasse")
196
+ P0 = param.Number(0.0, bounds=(0.0, 5.0), step=0.1, doc="Concentration initiale en produit")
197
+
198
+ @param.depends('mu_max', 'Ks', 'Yxs', 'Sin', 'X0', 'P0')
199
+ def view(self):
200
+ # Équations différentielles pour la fermentation
201
+ def model(y, t):
202
+ X, S, P = y
203
+ mu = self.mu_max * S / (self.Ks + S)
204
+ dXdt = mu * X
205
+ dSdt = -dXdt / self.Yxs
206
+ dPdt = 0.1 * dXdt # Production de produit proportionnelle à la croissance
207
+ return [dXdt, dSdt, dPdt]
208
+
209
+ # Conditions initiales
210
+ y0 = [self.X0, self.Sin, self.P0]
211
+
212
+ # Temps pour la simulation
213
+ t = np.linspace(0, 50, 500)
214
+
215
+ # Résoudre le système d'équations différentielles
216
+ solution = odeint(model, y0, t)
217
+
218
+ # Créer le graphique avec Plotly
219
+ fig = go.Figure()
220
+ fig.add_trace(go.Scatter(x=t, y=solution[:, 0], mode='lines', name='Biomasse (X)', line=dict(color='blue')))
221
+ fig.add_trace(go.Scatter(x=t, y=solution[:, 1], mode='lines', name='Substrat (S)', line=dict(color='green')))
222
+ fig.add_trace(go.Scatter(x=t, y=solution[:, 2], mode='lines', name='Produit (P)', line=dict(color='orange')))
223
+
224
+ # Mettre à jour la mise en page du graphique
225
+ fig.update_layout(
226
+ title='Modélisation de la Fermentation',
227
+ xaxis_title='Temps',
228
+ yaxis_title='Concentration',
229
+ legend_title='Légende',
230
+ height=400,
231
+ width=1500,
232
+ font_color="white",
233
+ template='plotly_dark'
234
+ )
235
+
236
+ return pn.pane.Plotly(fig)
237
+
238
+
239
+ def formulas(self):
240
+ return pn.Column(
241
+ pn.pane.LaTeX(r"""
242
+ $$
243
+ \mu = \frac{{\mu_{{\text{{max}}}} \cdot S}}{{K_s + S}}
244
+ $$
245
+ """, styles={'font-size': '16pt'}),
246
+ pn.pane.LaTeX(r"""
247
+ $$
248
+ \frac{{dX}}{{dt}} = \mu \cdot X
249
+ $$
250
+ """, styles={'font-size': '16pt'}),
251
+ pn.pane.LaTeX(r"""
252
+ $$
253
+ \frac{{dS}}{{dt}} = -\frac{1}{{Y_{{xs}}}} \cdot \frac{{dX}}{{dt}}
254
+ $$
255
+ """, styles={'font-size': '16pt'}),
256
+ pn.pane.LaTeX(r"""
257
+ $$
258
+ \frac{{dP}}{{dt}} = 0.1 \cdot \frac{{dX}}{{dt}}
259
+ $$
260
+ """, styles={'font-size': '16pt'})
261
+ )
262
+
263
+
264
+
265
+ def panel_view(self):
266
+ return pn.Column(
267
+ pn.Row(pn.Param(self, width=400), self.formulas()), # Aligner les widgets et les formules sur la même ligne
268
+ self.view # Appeler la vue pour qu'elle se mette à jour dynamiquement
269
+ )
270
+
271
+ # Classe pour l'estimation des paramètres par MCMC
272
+ class EstimationParametresFermentation(param.Parameterized):
273
+ # Paramètres pour l'estimation MCMC
274
+ n_marcheurs = param.Integer(32, bounds=(10, 100), doc="Nombre de marcheurs MCMC")
275
+ n_etapes = param.Integer(1000, bounds=(100, 5000), doc="Nombre d'étapes MCMC")
276
+ niveau_bruit = param.Number(0.1, bounds=(0.01, 0.5), doc="Niveau de bruit dans les données synthétiques")
277
+
278
+ def __init__(self, **params):
279
+ super().__init__(**params)
280
+ self.parametres_reels = {
281
+ 'mu_max': 0.8,
282
+ 'Ks': 10.0,
283
+ 'Yxs': 0.5
284
+ }
285
+
286
+ def generer_donnees_synthetiques(self, t):
287
+ def modele(y, t, mu_max, Ks, Yxs):
288
+ X, S, P = y
289
+ mu = mu_max * S / (Ks + S)
290
+ dXdt = mu * X
291
+ dSdt = -dXdt / Yxs
292
+ dPdt = 0.1 * dXdt
293
+ return [dXdt, dSdt, dPdt]
294
+
295
+ y0 = [0.1, 20.0, 0.0]
296
+ solution = odeint(modele, y0, t, args=(self.parametres_reels['mu_max'], self.parametres_reels['Ks'], self.parametres_reels['Yxs']))
297
+ bruit = np.random.normal(0, self.niveau_bruit, solution.shape)
298
+ return solution + bruit
299
+
300
+ def log_vraisemblance(self, theta, t, donnees):
301
+ mu_max, Ks, Yxs = theta
302
+
303
+ def modele(y, t, mu_max, Ks, Yxs):
304
+ X, S, P = y
305
+ mu = mu_max * S / (Ks + S)
306
+ dXdt = mu * X
307
+ dSdt = -dXdt / Yxs
308
+ dPdt = 0.1 * dXdt
309
+ return [dXdt, dSdt, dPdt]
310
+
311
+ y0 = [0.1, 20.0, 0.0]
312
+ try:
313
+ solution = odeint(modele, y0, t, args=(mu_max, Ks, Yxs))
314
+ sigma2 = self.niveau_bruit ** 2
315
+ return -0.5 * np.sum((donnees - solution) ** 2 / sigma2 + np.log(2 * np.pi * sigma2))
316
+ except:
317
+ return -np.inf
318
+
319
+ def log_apriori(self, theta):
320
+ mu_max, Ks, Yxs = theta
321
+ if 0.1 < mu_max < 2.0 and 1.0 < Ks < 50.0 and 0.1 < Yxs < 1.0:
322
+ return 0.0
323
+ return -np.inf
324
+
325
+ def log_probabilite(self, theta, t, donnees):
326
+ lp = self.log_apriori(theta)
327
+ if not np.isfinite(lp):
328
+ return -np.inf
329
+ return lp + self.log_vraisemblance(theta, t, donnees)
330
+
331
+ def lancer_mcmc(self):
332
+ t = np.linspace(0, 50, 50)
333
+ donnees = self.generer_donnees_synthetiques(t)
334
+ ndim = 3
335
+ pos = [
336
+ [self.parametres_reels['mu_max'], self.parametres_reels['Ks'], self.parametres_reels['Yxs']] + 1e-4 * np.random.randn(ndim)
337
+ for i in range(self.n_marcheurs)
338
+ ]
339
+ sampler = emcee.EnsembleSampler(self.n_marcheurs, ndim, self.log_probabilite, args=(t, donnees))
340
+ sampler.run_mcmc(pos, self.n_etapes, progress=True)
341
+ return sampler, t, donnees
342
+
343
+ @param.depends('n_marcheurs', 'n_etapes', 'niveau_bruit')
344
+ def vue(self):
345
+ sampler, t, donnees = self.lancer_mcmc()
346
+
347
+ fig = make_subplots(rows=2, cols=2,
348
+ subplot_titles=('Chaînes MCMC', 'Distribution postérieure', 'Ajustement du modèle', 'Corrélations des paramètres'))
349
+
350
+ echantillons = sampler.get_chain()
351
+
352
+ # Tracer les chaînes MCMC
353
+ for i in range(3):
354
+ fig.add_trace(go.Scatter(y=echantillons[:, 0, i], mode='lines', name=f'Paramètre {i+1}'), row=1, col=1)
355
+
356
+ echantillons_plats = sampler.get_chain(discard=100, thin=15, flat=True)
357
+
358
+ # Tracer les distributions postérieures
359
+ for i in range(3):
360
+ fig.add_trace(go.Histogram(x=echantillons_plats[:, i], name=f'Paramètre {i+1}', nbinsx=30), row=1, col=2)
361
+
362
+ params_med = np.median(echantillons_plats, axis=0)
363
+ y0 = [0.1, 20.0, 0.0]
364
+
365
+ # Définir une fonction de modèle pour odeint
366
+ def modele(y, t):
367
+ mu_max, Ks, Yxs = params_med
368
+ X, S, P = y
369
+ mu = mu_max * S / (Ks + S)
370
+ dXdt = mu * X
371
+ dSdt = -dXdt / Yxs
372
+ dPdt = 0.1 * dXdt
373
+ return [dXdt, dSdt, dPdt]
374
+
375
+ # Utiliser la fonction de modèle dans odeint
376
+ solution = odeint(modele, y0, t)
377
+
378
+ fig.add_trace(go.Scatter(x=t, y=donnees[:, 0], mode='markers', name='Données (X)', marker=dict(color='blue')), row=2, col=1)
379
+ fig.add_trace(go.Scatter(x=t, y=solution[:, 0], mode='lines', name='Modèle (X)', line=dict(color='red')), row=2, col=1)
380
+
381
+ # Tracer les corrélations des paramètres
382
+ fig.add_trace(go.Scatter(x=echantillons_plats[:, 0], y=echantillons_plats[:, 1],
383
+ mode='markers', marker=dict(size=2),
384
+ name='mu_max vs Ks'), row=2, col=2)
385
+
386
+ fig.update_layout(height=400, width=1200,
387
+ showlegend=True,
388
+ template='plotly_dark')
389
+
390
+ return pn.pane.Plotly(fig)
391
+
392
+ def vue_panneau(self):
393
+ return pn.Column(
394
+ pn.Row(
395
+ pn.Param(self.param,
396
+ widgets={
397
+ 'n_marcheurs': pn.widgets.IntSlider,
398
+ 'n_etapes': pn.widgets.IntSlider,
399
+ 'niveau_bruit': pn.widgets.FloatSlider}),
400
+ self.description_mcmc()
401
+ ),
402
+ self.vue # Correction pour supprimer '()' et référencer dynamiquement
403
+ )
404
+
405
+ def description_mcmc(self):
406
+ return pn.pane.Markdown("""
407
+ # Estimation des paramètres par MCMC
408
+ ## Cette section permet d'estimer les paramètres du modèle de fermentation en utilisant l'algorithme MCMC (Markov Chain Monte Carlo).
409
+ - ## **n_marcheurs**: Nombre de chaînes MCMC parallèles.
410
+ - ## **n_etapes**: Nombre d'itérations pour chaque chaîne.
411
+ - ## **niveau_bruit**: Niveau de bruit dans les données synthétiques.
412
+ ## Les graphiques montrent:
413
+ ### 1. L'évolution des chaînes MCMC.
414
+ ### 2. Les distributions postérieures des paramètres.
415
+ ### 3. L'ajustement du modèle aux données.
416
+ ### 4. Les corrélations entre les paramètres.
417
+ """)
418
+
419
+ # Informations personnelles et photo
420
+ photo = pn.pane.PNG(photo_url, width=200, height=200)
421
+ info = pn.Column(
422
+ pn.pane.Markdown(f"<div style='font-size:16px'><strong>Prénom</strong>: {prenom}</div>"),
423
+ pn.pane.Markdown(f"<div style='font-size:16px'><strong>Nom</strong>: {nom}</div>"),
424
+ pn.pane.Markdown(f"<div style='font-size:16px'><strong>Email</strong>: <a href='mailto:{email}'>{email}</a></div>"),
425
+ #pn.pane.Markdown(f"<div style='font-size:16px'><strong>WhatsApp</strong>: <a href='{whatsapps_url}'>WhatsApp</a></div>"),
426
+ #pn.pane.Markdown(f"<div style='font-size:16px'><strong>LinkedIn</strong>: <a href='{linkedin_url}'>LinkedIn</a></div>"),
427
+ # pn.pane.Markdown(f"<div style='font-size:16px'><strong>GitHub</strong>: <a href='{github_url}'>GitHub</a></div>"),
428
+ pn.pane.Markdown(f"<div style='font-size:16px'><strong>Université</strong>: {universite}</div>"),
429
+ pn.pane.Markdown(f"<div style='font-size:16px'><strong>Formation</strong>: {formation}</div>"),
430
+ pn.pane.Markdown(f"<div style='font-size:16px'><strong>Certificat</strong>: {certificat}</div>"),
431
+ pn.pane.Markdown(f"<div style='font-size:16px'><strong>Autre Projet </strong>: <a href='{TP_Scilab}'>Pour comprendre l'équation de Transport en 1D avec Scilab</a></div>"),
432
+ sizing_mode='stretch_width'
433
+ )
434
+
435
+
436
+
437
+ # Créer les applications
438
+ consumer_app = ConsumerResourceApp()
439
+ microbial_app = MicrobialDynamicsApp()
440
+ ferm_app = FermentationModelApp()
441
+
442
+ # Créer l'application
443
+ application_estimation_parametres = EstimationParametresFermentation()
444
+
445
+ # Afficher l'application Panel
446
+ #template = pn.template.ReactTemplate(title='Estimation des paramètres par MCMC')
447
+ #template.main[0, 0] = application_estimation_parametres.vue_panneau()
448
+
449
+
450
+ # Créer la mise en page
451
+ layout = pn.Row(
452
+ pn.Column(photo, info, width=300), # Ajouter la photo et les informations personnelles à gauche
453
+ pn.Column(
454
+ pn.Tabs(
455
+ ('Consumer-Resource Dynamics', consumer_app.panel_view()),
456
+ ('Microbial Dynamics', microbial_app.panel_view()),
457
+ (' Fermentation', ferm_app.panel_view()),
458
+ ('Estimation Parametres', application_estimation_parametres.vue_panneau())
459
+ )
460
+ )
461
+ )
462
+
463
+ # Ajouter le layout au template principal
464
+ template.main[0, 0] = layout
465
+
466
+ # Exécuter l'application
467
+ template.servable()