import numpy as np from scipy.integrate import odeint import emcee import panel as pn import param import plotly.graph_objects as go from plotly.subplots import make_subplots import plotly.io as pio # Définition du thème sombre pio.templates.default = "plotly_dark" # Initialiser l'extension Panel pn.extension('katex', 'mathjax', 'plotly') # Informations personnelles photo_url = "PPFB.png" prenom = "Yaya" nom = "Toure" email = "yaya.toure@unchk.edu.sn" whatsapps_url = "https://wa.me/message/GW7RWRW3GR4WN1" linkedin_url = "https://www.linkedin.com/in/yaya-toure-8251a4280/" github_url = "https://github.com/CodingYayaToure" universite = "UNCHK" formation = "Licence Analyse Numerique et Modelisation | Master Calcul scientifique et Modelisation" certificat = "Collecte et Fouille de Donnees (UADB-CNAM Paris)" TP_Scilab = "https://codingyayatoure.github.io/Equation_de_Transport_en_1D_par_la_Methode_des_Differences_Finis/" # Créer le template du tableau de bord template = pn.template.ReactTemplate(title='Progiciel de Simulation des Interactions Microbiennes en Fermentation de Jus de Légumes. | Approche Modélisation mathématique ') # Classe pour la dynamique Consumer-Resource class ConsumerResourceApp(param.Parameterized): # Paramètres du modèle r = param.Number(10.0, bounds=(0.1, 20.0), step=0.1, doc="Taux d'approvisionnement en ressources") d = param.Number(0.1, bounds=(0.01, 1.0), step=0.01, doc="Taux de mortalité des consommateurs") c = param.Number(0.1, bounds=(0.01, 1.0), step=0.01, doc="Coefficient de consommation") K = param.Number(1.0, bounds=(0.1, 10.0), step=0.1, doc="Concentration à laquelle la croissance est à moitié maximale") X0 = param.Number(1.0, bounds=(0.1, 10.0), step=0.1, doc="Population initiale des consommateurs") S0 = param.Number(5.0, bounds=(0.1, 20.0), step=0.1, doc="Concentration initiale de la ressource") @param.depends('r', 'd', 'c', 'K', 'X0', 'S0') def view(self): # Fonction de croissance dépendante de la ressource (Monod) def mu(S): return S / (self.K + S) # Équations différentielles def model(y, t): X, S = y dXdt = mu(S) * X - self.d * X # Dynamique des consommateurs dSdt = self.r - self.c * mu(S) * X # Dynamique des ressources return [dXdt, dSdt] # Conditions initiales y0 = [self.X0, self.S0] # Temps pour la simulation (en heures) t_hours = np.linspace(0, 100, 500) # Résoudre le système d'équations différentielles solution = odeint(model, y0, t_hours) # Créer les graphiques avec Plotly en utilisant des sous-graphiques fig = make_subplots( rows=1, cols=3, specs=[[{'type': 'scatter'}, {'type': 'scatter'}, {'type': 'scatter'}]], subplot_titles=["Population des Consommateurs (X)", "Concentration de la Ressource (S)", "Population et Ressource sur le même repère"] ) # Ajouter la trace pour les consommateurs 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) # Ajouter la trace pour les ressources 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) # Ajouter la trace pour les consommateurs et les ressources sur le même graphique 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) 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) # Mettre à jour la mise en page du graphique fig.update_layout( title='Dynamique du Modèle Consumer-Resource', xaxis_title='Temps', yaxis_title='Population / Concentration', legend_title='Légende', height=400, width=1500, font_color="white", template='plotly_dark' ) return pn.pane.Plotly(fig) def panel_view(self): return pn.Column( pn.Row(pn.Param(self, width=400), self.formulas()), # Aligner les widgets et les formules sur la même ligne self.view # Appeler la vue pour qu'elle se mette à jour dynamiquement ) def formulas(self): return pn.Column( pn.pane.LaTeX(r""" $$ \text{Fonction de croissance : } \mu(S) = \frac{S}{K + S} $$ """, styles={'font-size': '16pt'}), pn.pane.LaTeX(r""" $$ \text{Dynamique des consommateurs : } \frac{dX}{dt} = \mu(S) \times X - d \times X $$ """, styles={'font-size': '16pt'}), pn.pane.LaTeX(r""" $$ \text{Dynamique des ressources : } \frac{dS}{dt} = r - c \times \mu(S) \times X $$ """, styles={'font-size': '16pt'}) ) # Classe pour la dynamique des populations microbiennes class MicrobialDynamicsApp(param.Parameterized): # Paramètres du modèle pour la dynamique des populations microbiennes r1 = param.Number(0.5, bounds=(0.1, 1.0), step=0.01, doc="Taux de croissance espèce 1") r2 = param.Number(0.3, bounds=(0.1, 1.0), step=0.01, doc="Taux de croissance espèce 2") K1 = param.Number(100, bounds=(10, 200), step=1, doc="Capacité de charge espèce 1") K2 = param.Number(80, bounds=(10, 200), step=1, doc="Capacité de charge espèce 2") alpha = param.Number(0.01, bounds=(0.001, 0.1), step=0.001, doc="Compétition espèce 2 sur espèce 1") beta = param.Number(0.01, bounds=(0.001, 0.1), step=0.001, doc="Compétition espèce 1 sur espèce 2") @param.depends('r1', 'r2', 'K1', 'K2', 'alpha', 'beta') def view(self): # Équations différentielles pour les populations microbiennes def model(y, t): X1, X2 = y dX1dt = self.r1 * X1 * (1 - (X1 + self.alpha * X2) / self.K1) dX2dt = self.r2 * X2 * (1 - (X2 + self.beta * X1) / self.K2) return [dX1dt, dX2dt] # Conditions initiales y0 = [10, 5] # Population initiale des deux espèces # Temps pour la simulation t = np.linspace(0, 50, 500) # Résoudre le système d'équations différentielles solution = odeint(model, y0, t) # Créer le graphique avec Plotly fig = go.Figure() fig.add_trace(go.Scatter(x=t, y=solution[:, 0], mode='lines', name='Espèce 1', line=dict(color='red'))) fig.add_trace(go.Scatter(x=t, y=solution[:, 1], mode='lines', name='Espèce 2', line=dict(color='purple'))) # Mettre à jour la mise en page du graphique fig.update_layout( title='Dynamique des Populations Microbiennes', xaxis_title='Temps', yaxis_title='Population', legend_title='Légende', height=400, width=1500, font_color="white", template='plotly_dark' ) return pn.pane.Plotly(fig) def panel_view(self): return pn.Column( pn.Row(pn.Param(self, width=400), self.formulas()), # Aligner les widgets et les formules sur la même ligne self.view # Appeler la vue pour qu'elle se mette à jour dynamiquement ) def formulas(self): return pn.Column( pn.pane.LaTeX(r""" $$ \frac{{dX_1}}{{dt}} = r_1 X_1 \left( 1 - \frac{{X_1 + \alpha X_2}}{{K_1}} \right) $$ """, styles={'font-size': '16pt'}), pn.pane.LaTeX(r""" $$ \frac{{dX_2}}{{dt}} = r_2 X_2 \left( 1 - \frac{{X_2 + \beta X_1}}{{K_2}} \right) $$ """, styles={'font-size': '16pt'}) ) # Classe pour la modélisation de la fermentation class FermentationModelApp(param.Parameterized): # Paramètres du modèle de fermentation mu_max = param.Number(0.8, bounds=(0.1, 2.0), step=0.1, doc="Taux de croissance maximal") Ks = param.Number(10.0, bounds=(1.0, 50.0), step=1.0, doc="Constante de demi-saturation") Yxs = param.Number(0.5, bounds=(0.1, 1.0), step=0.1, doc="Rendement de croissance sur substrat") Sin = param.Number(20.0, bounds=(10.0, 100.0), step=1.0, doc="Concentration initiale en substrat") X0 = param.Number(0.1, bounds=(0.01, 5.0), step=0.01, doc="Concentration initiale en biomasse") P0 = param.Number(0.0, bounds=(0.0, 5.0), step=0.1, doc="Concentration initiale en produit") @param.depends('mu_max', 'Ks', 'Yxs', 'Sin', 'X0', 'P0') def view(self): # Équations différentielles pour la fermentation def model(y, t): X, S, P = y mu = self.mu_max * S / (self.Ks + S) dXdt = mu * X dSdt = -dXdt / self.Yxs dPdt = 0.1 * dXdt # Production de produit proportionnelle à la croissance return [dXdt, dSdt, dPdt] # Conditions initiales y0 = [self.X0, self.Sin, self.P0] # Temps pour la simulation t = np.linspace(0, 50, 500) # Résoudre le système d'équations différentielles solution = odeint(model, y0, t) # Créer le graphique avec Plotly fig = go.Figure() fig.add_trace(go.Scatter(x=t, y=solution[:, 0], mode='lines', name='Biomasse (X)', line=dict(color='blue'))) fig.add_trace(go.Scatter(x=t, y=solution[:, 1], mode='lines', name='Substrat (S)', line=dict(color='green'))) fig.add_trace(go.Scatter(x=t, y=solution[:, 2], mode='lines', name='Produit (P)', line=dict(color='orange'))) # Mettre à jour la mise en page du graphique fig.update_layout( title='Modélisation de la Fermentation', xaxis_title='Temps', yaxis_title='Concentration', legend_title='Légende', height=400, width=1500, font_color="white", template='plotly_dark' ) return pn.pane.Plotly(fig) def formulas(self): return pn.Column( pn.pane.LaTeX(r""" $$ \mu = \frac{{\mu_{{\text{{max}}}} \cdot S}}{{K_s + S}} $$ """, styles={'font-size': '16pt'}), pn.pane.LaTeX(r""" $$ \frac{{dX}}{{dt}} = \mu \cdot X $$ """, styles={'font-size': '16pt'}), pn.pane.LaTeX(r""" $$ \frac{{dS}}{{dt}} = -\frac{1}{{Y_{{xs}}}} \cdot \frac{{dX}}{{dt}} $$ """, styles={'font-size': '16pt'}), pn.pane.LaTeX(r""" $$ \frac{{dP}}{{dt}} = 0.1 \cdot \frac{{dX}}{{dt}} $$ """, styles={'font-size': '16pt'}) ) def panel_view(self): return pn.Column( pn.Row(pn.Param(self, width=400), self.formulas()), # Aligner les widgets et les formules sur la même ligne self.view # Appeler la vue pour qu'elle se mette à jour dynamiquement ) # Classe pour l'estimation des paramètres par MCMC class EstimationParametresFermentation(param.Parameterized): # Paramètres pour l'estimation MCMC n_marcheurs = param.Integer(32, bounds=(10, 100), doc="Nombre de marcheurs MCMC") n_etapes = param.Integer(1000, bounds=(100, 5000), doc="Nombre d'étapes MCMC") niveau_bruit = param.Number(0.1, bounds=(0.01, 0.5), doc="Niveau de bruit dans les données synthétiques") def __init__(self, **params): super().__init__(**params) self.parametres_reels = { 'mu_max': 0.8, 'Ks': 10.0, 'Yxs': 0.5 } def generer_donnees_synthetiques(self, t): def modele(y, t, mu_max, Ks, Yxs): X, S, P = y mu = mu_max * S / (Ks + S) dXdt = mu * X dSdt = -dXdt / Yxs dPdt = 0.1 * dXdt return [dXdt, dSdt, dPdt] y0 = [0.1, 20.0, 0.0] solution = odeint(modele, y0, t, args=(self.parametres_reels['mu_max'], self.parametres_reels['Ks'], self.parametres_reels['Yxs'])) bruit = np.random.normal(0, self.niveau_bruit, solution.shape) return solution + bruit def log_vraisemblance(self, theta, t, donnees): mu_max, Ks, Yxs = theta def modele(y, t, mu_max, Ks, Yxs): X, S, P = y mu = mu_max * S / (Ks + S) dXdt = mu * X dSdt = -dXdt / Yxs dPdt = 0.1 * dXdt return [dXdt, dSdt, dPdt] y0 = [0.1, 20.0, 0.0] try: solution = odeint(modele, y0, t, args=(mu_max, Ks, Yxs)) sigma2 = self.niveau_bruit ** 2 return -0.5 * np.sum((donnees - solution) ** 2 / sigma2 + np.log(2 * np.pi * sigma2)) except: return -np.inf def log_apriori(self, theta): mu_max, Ks, Yxs = theta if 0.1 < mu_max < 2.0 and 1.0 < Ks < 50.0 and 0.1 < Yxs < 1.0: return 0.0 return -np.inf def log_probabilite(self, theta, t, donnees): lp = self.log_apriori(theta) if not np.isfinite(lp): return -np.inf return lp + self.log_vraisemblance(theta, t, donnees) def lancer_mcmc(self): t = np.linspace(0, 50, 50) donnees = self.generer_donnees_synthetiques(t) ndim = 3 pos = [ [self.parametres_reels['mu_max'], self.parametres_reels['Ks'], self.parametres_reels['Yxs']] + 1e-4 * np.random.randn(ndim) for i in range(self.n_marcheurs) ] sampler = emcee.EnsembleSampler(self.n_marcheurs, ndim, self.log_probabilite, args=(t, donnees)) sampler.run_mcmc(pos, self.n_etapes, progress=True) return sampler, t, donnees @param.depends('n_marcheurs', 'n_etapes', 'niveau_bruit') def vue(self): sampler, t, donnees = self.lancer_mcmc() fig = make_subplots(rows=2, cols=2, subplot_titles=('Chaînes MCMC', 'Distribution postérieure', 'Ajustement du modèle', 'Corrélations des paramètres')) echantillons = sampler.get_chain() # Tracer les chaînes MCMC for i in range(3): fig.add_trace(go.Scatter(y=echantillons[:, 0, i], mode='lines', name=f'Paramètre {i+1}'), row=1, col=1) echantillons_plats = sampler.get_chain(discard=100, thin=15, flat=True) # Tracer les distributions postérieures for i in range(3): fig.add_trace(go.Histogram(x=echantillons_plats[:, i], name=f'Paramètre {i+1}', nbinsx=30), row=1, col=2) params_med = np.median(echantillons_plats, axis=0) y0 = [0.1, 20.0, 0.0] # Définir une fonction de modèle pour odeint def modele(y, t): mu_max, Ks, Yxs = params_med X, S, P = y mu = mu_max * S / (Ks + S) dXdt = mu * X dSdt = -dXdt / Yxs dPdt = 0.1 * dXdt return [dXdt, dSdt, dPdt] # Utiliser la fonction de modèle dans odeint solution = odeint(modele, y0, t) fig.add_trace(go.Scatter(x=t, y=donnees[:, 0], mode='markers', name='Données (X)', marker=dict(color='blue')), row=2, col=1) fig.add_trace(go.Scatter(x=t, y=solution[:, 0], mode='lines', name='Modèle (X)', line=dict(color='red')), row=2, col=1) # Tracer les corrélations des paramètres fig.add_trace(go.Scatter(x=echantillons_plats[:, 0], y=echantillons_plats[:, 1], mode='markers', marker=dict(size=2), name='mu_max vs Ks'), row=2, col=2) fig.update_layout(height=400, width=1200, showlegend=True, template='plotly_dark') return pn.pane.Plotly(fig) def vue_panneau(self): return pn.Column( pn.Row( pn.Param(self.param, widgets={ 'n_marcheurs': pn.widgets.IntSlider, 'n_etapes': pn.widgets.IntSlider, 'niveau_bruit': pn.widgets.FloatSlider}), self.description_mcmc() ), self.vue # Correction pour supprimer '()' et référencer dynamiquement ) def description_mcmc(self): return pn.pane.Markdown(""" # Estimation des paramètres par MCMC ## Cette section permet d'estimer les paramètres du modèle de fermentation en utilisant l'algorithme MCMC (Markov Chain Monte Carlo). - ## **n_marcheurs**: Nombre de chaînes MCMC parallèles. - ## **n_etapes**: Nombre d'itérations pour chaque chaîne. - ## **niveau_bruit**: Niveau de bruit dans les données synthétiques. ## Les graphiques montrent: ### 1. L'évolution des chaînes MCMC. ### 2. Les distributions postérieures des paramètres. ### 3. L'ajustement du modèle aux données. ### 4. Les corrélations entre les paramètres. """) # Informations personnelles et photo photo = pn.pane.PNG(photo_url, width=200, height=200) info = pn.Column( pn.pane.Markdown(f"