Projet Calcul Intelligent Travail réalisé par Yosr Barghouti 3IDL2 https://abs-fuzzy-logic.onrender.com// Application web Flask interactive pour simuler un système de Freinage d'Urgence Automatique (AEB) utilisant la logique floue et la méthode d'inférence de Mamdani.
- Description
- Qu'est-ce que la Logique Floue ?
- Processus d'Inférence de Mamdani
- Technologies et Bibliothèques
- Fonctionnalités
- Installation
- Utilisation
- Architecture Technique
- API REST
- Développement
Ce projet implémente un contrôleur flou pour un système ABS qui calcule la pression de freinage optimale en fonction de :
- Distance à l'obstacle : 0-100 mètres
- Vitesse relative du véhicule : 0-100 km/h
Le système utilise 9 règles floues et la méthode d'inférence de Mamdani avec 3 méthodes de défuzzification comparées en temps réel :
- Centroïde (COG) - Recommandée pour ce système
- Premier Maximum (FOM) - Décision rapide
- Milieu de Noyau (MOM) - Compromis
La logique floue est une approche mathématique permettant de gérer l'incertitude et l'imprécision dans les systèmes de contrôle. Contrairement à la logique binaire (vrai/faux), la logique floue permet des valeurs intermédiaires.
En logique classique :
- Distance = 35m → "Proche" OU "Moyen" (choix binaire)
En logique floue :
- Distance = 35m → "Proche" à 60% ET "Moyen" à 40% (transition graduelle)
Cette capacité à gérer les transitions douces rend la logique floue idéale pour les systèmes de contrôle automobile comme l'ABS, où les décisions doivent être progressives et non brusques.
Décisions progressives : Pas de freinage brutal Gestion de l'incertitude : Capteurs imprécis, conditions variables Règles intuitives : "Si distance proche ET vitesse élevée ALORS pression urgente" Robustesse : Fonctionne dans des conditions variées
Le système utilise la méthode d'inférence de Mamdani qui se déroule en 5 étapes distinctes. Cette section explique chaque étape en détail avec des visualisations.
Objectif : Convertir les valeurs d'entrée nettes (crisp) en degrés d'appartenance aux ensembles flous.
Exemple : Pour Distance = 30m et Vitesse = 60 km/h
# Calcul des degrés d'appartenance pour la Distance
Distance = 30m
μ_Proche(30) = 0.250 # 25% d'appartenance à "Proche"
μ_Moyen(30) = 0.333 # 33% d'appartenance à "Moyen"
μ_Lointain(30) = 0.000 # 0% d'appartenance à "Lointain"
# Calcul des degrés d'appartenance pour la Vitesse
Vitesse = 60 km/h
μ_Faible(60) = 0.000 # 0% d'appartenance à "Faible"
μ_Moyen(60) = 0.667 # 67% d'appartenance à "Moyen"
μ_Élevé(60) = 0.333 # 33% d'appartenance à "Élevé"Code scikit-fuzzy :
import skfuzzy as fuzz
from skfuzzy import control as ctrl
# Définition des univers de discours
distance = ctrl.Antecedent(np.arange(0, 101, 1), 'distance')
velocity = ctrl.Antecedent(np.arange(0, 101, 1), 'velocity')
# Définition des fonctions d'appartenance (triangulaires)
distance['C'] = fuzz.trimf(distance.universe, [0, 0, 40]) # Proche
distance['M'] = fuzz.trimf(distance.universe, [20, 50, 80]) # Moyen
distance['L'] = fuzz.trimf(distance.universe, [60, 100, 100])# Lointain
# Calcul de l'appartenance pour une valeur donnée
distance_value = 30
membership_C = fuzz.interp_membership(distance.universe, distance['C'].mf, distance_value)Objectif : Calculer le degré d'activation de chaque règle en appliquant l'opérateur AND (min) sur les antécédents.
Formule : α_règle = min(μ_Distance, μ_Vitesse)
Exemple : Règles activées pour Distance = 30m, Vitesse = 60 km/h
# Règle 5: SI Distance = Moyen ET Vitesse = Moyen ALORS Pression = Moyenne
α_R5 = min(μ_Moyen_Distance, μ_Moyen_Vitesse)
α_R5 = min(0.333, 0.667) = 0.333
# Règle 6: SI Distance = Moyen ET Vitesse = Élevé ALORS Pression = Élevée
α_R6 = min(μ_Moyen_Distance, μ_Élevé_Vitesse)
α_R6 = min(0.333, 0.333) = 0.333
# Règle 8: SI Distance = Proche ET Vitesse = Moyen ALORS Pression = Élevée
α_R8 = min(μ_Proche_Distance, μ_Moyen_Vitesse)
α_R8 = min(0.250, 0.667) = 0.250Code Python :
def activate_rule(distance_membership, velocity_membership):
"""Calcule l'activation d'une règle avec l'opérateur min"""
return min(distance_membership, velocity_membership)
# Exemple
activation_R5 = activate_rule(0.333, 0.667) # Retourne 0.333Objectif : Tronquer la fonction d'appartenance de la conclusion par le degré d'activation de la règle.
Formule : μ'_conclusion(x) = min(α_règle, μ_conclusion(x))
Exemple : Pour la règle R5 activée à α = 0.333 avec conclusion "Pression Moyenne"
# Fonction d'appartenance originale de "Pression Moyenne"
μ_Moyen_original(x) = trimf(x, [25, 50, 75])
# Fonction tronquée par l'activation
μ'_Moyen(x) = min(0.333, μ_Moyen_original(x))
# Résultat : La fonction est "coupée" à la hauteur 0.333Code scikit-fuzzy :
def mamdani_implication(activation_level, output_mf):
"""Applique l'implication de Mamdani (min)"""
return np.minimum(activation_level, output_mf)
# Exemple
original_mf = fuzz.trimf(np.arange(0, 101, 1), [25, 50, 75])
truncated_mf = mamdani_implication(0.333, original_mf)Objectif : Combiner toutes les fonctions d'appartenance tronquées en une seule fonction globale.
Formule : μ_global(x) = max(μ'_R1(x), μ'_R2(x), ..., μ'_R9(x))
Visualisation : Toutes les fonctions impliquées sont superposées, et l'opérateur max prend la valeur maximale à chaque point.
# Agrégation de 3 règles activées
μ'_R5(x) = min(0.333, μ_Moyen(x)) # Pression Moyenne
μ'_R6(x) = min(0.333, μ_Élevé(x)) # Pression Élevée
μ'_R8(x) = min(0.250, μ_Élevé(x)) # Pression Élevée
# Fonction agrégée
μ_global(x) = max(μ'_R5(x), μ'_R6(x), μ'_R8(x))Code Python :
def aggregate_rules(implicated_functions):
"""Agrège toutes les fonctions avec l'opérateur max"""
aggregated = np.zeros_like(implicated_functions[0])
for func in implicated_functions:
aggregated = np.maximum(aggregated, func)
return aggregatedObjectif : Convertir la fonction d'appartenance floue agrégée en une valeur numérique unique (pression de freinage).
Le système compare 3 méthodes de défuzzification en temps réel :
Formule : x_COG = ∫x·μ(x)dx / ∫μ(x)dx
Description : Centre de gravité de la surface sous la fonction agrégée.
def defuzzify_centroid(universe, aggregated_mf):
"""Calcule le centroïde (centre de gravité)"""
numerator = np.sum(universe * aggregated_mf)
denominator = np.sum(aggregated_mf)
return numerator / denominator if denominator != 0 else 0
# Exemple : Centroïde = 59.24Avantages : Prend en compte toute la fonction, résultat lisse et stable Inconvénients : Plus coûteux en calcul
Formule : x_FOM = min{x | μ(x) = max(μ)}
Description : Première valeur où la fonction atteint son maximum.
def defuzzify_first_of_maxima(universe, aggregated_mf):
"""Trouve le premier maximum"""
max_value = np.max(aggregated_mf)
indices = np.where(aggregated_mf == max_value)[0]
return universe[indices[0]]
# Exemple : FOM = 34.00Avantages : Rapide, favorise les décisions immédiates Inconvénients : Ignore une partie de l'information
Formule : x_MOM = (a + b) / 2 où μ(a) = μ(b) = max(μ)
Description : Milieu du plateau maximal.
def defuzzify_mean_of_maxima(universe, aggregated_mf):
"""Calcule le milieu du noyau"""
max_value = np.max(aggregated_mf)
indices = np.where(aggregated_mf == max_value)[0]
return (universe[indices[0]] + universe[indices[-1]]) / 2
# Exemple : MOM = 50.00Avantages : Compromis entre COG et FOM Inconvénients : Peut donner des résultats inattendus si le plateau est large
Comparaison Visuelle :
Distance = 30m, Vitesse = 60 km/h
┌─────────────────────┬─────────┬────────────┐
│ Méthode │ Valeur │ Niveau │
├─────────────────────┼─────────┼────────────┤
│ Centroïde (COG) │ 59.24 │ Moyen │ ← Recommandé
│ Premier Max (FOM) │ 34.00 │ Faible │
│ Milieu Noyau (MOM) │ 50.00 │ Moyen │
└─────────────────────┴─────────┴────────────┘
Description : Framework web Python léger et flexible pour créer des applications web.
Utilisation dans le projet :
- Serveur HTTP pour l'interface web
- Routes API REST pour la communication frontend-backend
- Rendu des templates HTML avec Jinja2
from flask import Flask, render_template, request, jsonify
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/simulate', methods=['POST'])
def simulate():
data = request.get_json()
result = controller.simulate(data['distance'], data['velocity'])
return jsonify(result)Description : Bibliothèque Python pour la logique floue et les systèmes de contrôle flou.
Utilisation dans le projet :
- Définition des variables floues (Antecedent, Consequent)
- Création des fonctions d'appartenance (trimf, trapmf)
- Calcul des degrés d'appartenance (fuzzify)
- Inférence de Mamdani et défuzzification
import skfuzzy as fuzz
from skfuzzy import control as ctrl
# Création des variables floues
distance = ctrl.Antecedent(np.arange(0, 101, 1), 'distance')
velocity = ctrl.Antecedent(np.arange(0, 101, 1), 'velocity')
pressure = ctrl.Consequent(np.arange(0, 101, 1), 'pressure')
# Définition des fonctions d'appartenance triangulaires
distance['C'] = fuzz.trimf(distance.universe, [0, 0, 40])
distance['M'] = fuzz.trimf(distance.universe, [20, 50, 80])
distance['L'] = fuzz.trimf(distance.universe, [60, 100, 100])Description : Bibliothèque fondamentale pour le calcul scientifique en Python.
Utilisation dans le projet :
- Manipulation des tableaux pour les univers de discours
- Opérations vectorisées pour les calculs d'appartenance
- Fonctions mathématiques (min, max, sum) pour l'inférence
import numpy as np
# Création de l'univers de discours
universe = np.arange(0, 101, 1)
# Opérations vectorisées pour l'implication
truncated_mf = np.minimum(activation_level, output_mf)
# Agrégation avec max
aggregated = np.maximum(func1, func2)Description : Bibliothèque de visualisation Python (utilisée en backend pour les calculs).
Utilisation dans le projet :
- Dépendance de scikit-fuzzy
- Utilisée pour générer des données de visualisation
Description : Framework CSS responsive pour créer des interfaces web modernes.
Utilisation dans le projet :
- Grille responsive (col-lg-4, col-lg-8)
- Composants UI (cards, buttons, progress bars)
- Thème et couleurs (bg-primary, text-white)
- Badges et alertes
Description : Bibliothèque JavaScript pour créer des graphiques interactifs.
Utilisation dans le projet :
- Graphiques des fonctions d'appartenance (distance, vitesse, pression)
- Visualisation des étapes d'inférence (fuzzification, implication, agrégation)
- Graphiques interactifs avec zoom, pan, hover
// Affichage des fonctions d'appartenance
Plotly.newPlot('mfDistance', [
{
x: data.distance.universe,
y: data.distance.C,
name: 'Proche',
line: {color: '#dc3545'}
},
{
x: data.distance.universe,
y: data.distance.M,
name: 'Moyen',
line: {color: '#ffc107'}
},
{
x: data.distance.universe,
y: data.distance.L,
name: 'Lointain',
line: {color: '#28a745'}
}
], {
title: 'Distance',
xaxis: {title: 'Distance (m)'},
yaxis: {title: 'Degré d\'appartenance'}
});Description : Langage de programmation pour l'interactivité frontend.
Utilisation dans le projet :
- Communication avec l'API Flask (fetch)
- Manipulation du DOM
- Gestion de l'état de l'application (appState)
- Debouncing pour les curseurs
// Debouncing pour éviter les appels API excessifs
let inferenceDebounceTimer;
distanceSlider.addEventListener('input', function() {
appState.distance = parseFloat(this.value);
clearTimeout(inferenceDebounceTimer);
inferenceDebounceTimer = setTimeout(() => {
runFullInference();
}, 300); // Attend 300ms après la dernière modification
});- Auto-inférence : Le processus d'inférence se déclenche automatiquement lors de l'ajustement des curseurs
- Debouncing intelligent : 300ms de délai pour éviter les appels API excessifs
- Visualisation instantanée : Mise à jour en temps réel de toutes les étapes d'inférence
-
Curseurs de paramètres :
- Distance : 0-100 mètres (valeur par défaut: 30m)
- Vitesse : 0-100 km/h (valeur par défaut: 60 km/h)
-
Scénarios pré-définis :
- Normal : Distance 80m, Vitesse 40 km/h - Conduite sécuritaire
- Dangereux : Distance 25m, Vitesse 70 km/h - Approche rapide
- Critique : Distance 10m, Vitesse 90 km/h - Risque imminent
-
Base de règles :
- Matrice de décision 3×3 avec code couleur
- 9 règles floues organisées visuellement
-
Fonctions d'appartenance :
- Graphiques compacts pour Distance, Vitesse et Pression
- Visualisation des ensembles flous (Proche/Moyen/Lointain, etc.)
-
Règles activées :
- Liste des règles déclenchées avec leurs poids
- Barres de progression compactes (height: 6px)
- Tri par poids décroissant
-
Degrés d'appartenance :
- Affichage compact des valeurs de fuzzification
- Pourcentages d'appartenance à chaque ensemble flou
Affichage côte-à-côte des 3 méthodes de défuzzification :
┌──────────────────┬──────────────────┬──────────────────┐
│ Centroïde (COG) │ Premier Max (FOM)│ Milieu Noyau (MOM)│
│ 59.24 │ 34.00 │ 50.00 │
│ Recommandé │ Faible │ Moyen │
└──────────────────┴──────────────────┴──────────────────┘
- Centroïde (bleu) : Méthode recommandée pour ce système ABS
- Premier Maximum (vert) : Première valeur maximale
- Milieu de Noyau (orange) : Compromis entre COG et FOM
Visualisation pédagogique complète du processus de Mamdani :
Étape 1 : Fuzzification
- Graphiques avec ligne verticale marquant la valeur actuelle
- Tableau des degrés d'appartenance calculés
- Points marqués sur les courbes des ensembles flous
Étape 2 : Activation des Règles
- Liste des règles activées avec formules (min)
- Barres de progression montrant l'activation
- Affichage des calculs détaillés
Étape 3 : Implication (Mamdani)
- Graphiques superposés pour chaque règle
- Fonction originale (ligne pointillée)
- Fonction tronquée (zone colorée)
Étape 4 : Agrégation
- Toutes les fonctions impliquées en transparence
- Fonction agrégée finale en surbrillance
- Zone colorée sous la courbe
Étape 5 : Défuzzification
- Graphique avec 3 marqueurs verticaux colorés
- Tableau comparatif des méthodes
- Recommandation expliquée
abs/
├── app.py # Serveur Flask et API REST
├── fuzzy_controller.py # Logique du contrôleur flou
├── requirements.txt # Dépendances Python
├── templates/
│ └── index.html # Interface utilisateur
├── static/
│ ├── css/
│ │ └── style.css # Styles personnalisés
│ └── js/
│ └── main.js # Logique frontend
└── README.md # Ce fichier
- Python 3.8 ou supérieur
- pip (gestionnaire de paquets Python)
-
Cloner ou télécharger le projet
-
Installer les dépendances
pip install Flask scikit-fuzzy numpy matplotlibOu avec le fichier requirements.txt :
pip install -r requirements.txtNote : Si vous rencontrez des problèmes avec numpy==1.24.3, vous pouvez installer une version plus récente :
pip install Flask scikit-fuzzy numpy matplotlibpython app.pyL'application sera accessible à l'adresse : http://localhost:5000
Ajuster les paramètres :
- Utilisez les curseurs pour modifier la distance (0-100m) et la vitesse (0-100 km/h)
- L'inférence se déclenche automatiquement après 300ms d'inactivité (debouncing)
- Pas besoin de cliquer sur un bouton "Simuler" - tout est en temps réel !
Scénarios rapides :
- Cliquez sur les boutons Normal, Dangereux ou Critique pour charger des configurations pré-définies
- Idéal pour comparer différentes situations rapidement
Visualiser les règles et fonctions :
- Matrice 3×3 : Vue d'ensemble de la stratégie de freinage
- Graphiques des fonctions d'appartenance : Voir les ensembles flous Distance, Vitesse, Pression
- Règles activées : Quelles règles sont déclenchées pour les valeurs actuelles
- Degrés d'appartenance : Pourcentages d'appartenance aux ensembles flous
Résultats de Défuzzification (en haut) :
L'application affiche 3 méthodes de défuzzification côte-à-côte :
┌──────────────────────────────────────────────────────────┐
│ Centroïde (COG) │ Premier Max (FOM) │ Milieu Noyau (MOM) │
│ 59.24 │ 34.00 │ 50.00 │
│ Recommandé │ Faible │ Moyen │
└──────────────────────────────────────────────────────────┘
- Centroïde (bleu) : Méthode recommandée - Centre de gravité de la fonction agrégée
- Premier Maximum (vert) : Première valeur où μ(x) atteint son maximum
- Milieu de Noyau (orange) : Moyenne du plateau maximal
Code couleur du niveau de pression :
- Faible : Pression < 35 - Distance sécuritaire, freinage léger
- Moyen : Pression 35-75 - Freinage modéré nécessaire
- Élevé : Pression 75-95 - Freinage important requis
- Urgent : Pression > 95 - Freinage d'urgence maximal
Processus d'Inférence Détaillé (en bas) :
Les 5 étapes de Mamdani sont affichées automatiquement sous les résultats :
- Fuzzification : Graphiques montrant où se situent vos valeurs sur les fonctions d'appartenance
- Activation : Calculs min(μ_distance, μ_vitesse) pour chaque règle
- Implication : Fonctions de sortie tronquées par les activations
- Agrégation : Combinaison de toutes les fonctions avec max
- Défuzzification : Graphique montrant les 3 méthodes avec leurs marqueurs
L'application expose plusieurs endpoints API :
Simule le contrôleur flou avec des paramètres donnés.
Corps de la requête (JSON) :
{
"distance": 50,
"velocity": 70
}Réponse (JSON) :
{
"pressure": 75.23,
"activated_rules": [...],
"membership_values": {...},
"explanation": "..."
}Retourne les données des fonctions d'appartenance pour les visualisations Plotly.
Réponse (JSON) :
{
"distance": {
"universe": [0, 1, 2, ..., 100],
"C": [1.0, 0.975, 0.95, ..., 0.0],
"M": [0.0, 0.0, 0.0, ..., 0.0],
"L": [0.0, 0.0, 0.0, ..., 1.0]
},
"velocity": {...},
"pressure": {...}
}Liste des scénarios pré-définis.
Réponse (JSON) :
{
"scenarios": {
"normal": {"distance": 80, "velocity": 40, "description": "..."},
"dangerous": {"distance": 25, "velocity": 70, "description": "..."},
"critical": {"distance": 10, "velocity": 90, "description": "..."}
}
}Charge et simule un scénario (normal, dangerous, critical).
Exemple :
curl -X POST http://localhost:5000/api/scenario/dangerousRetourne la base de règles complète avec leurs définitions.
Réponse (JSON) :
{
"rules": [
{
"id": "R1",
"distance_term": "L",
"velocity_term": "F",
"output_term": "F",
"description": "Si Distance = Lointain ET Vitesse = Faible ALORS Pression = Faible"
},
...
]
}Retourne toutes les étapes détaillées du processus d'inférence floue.
Corps de la requête (JSON) :
{
"distance": 30,
"velocity": 60
}Réponse (JSON) :
{
"inputs": {...},
"step1_fuzzification": {...},
"step2_rule_activation": {...},
"step3_implication": {...},
"step4_aggregation": {...},
"step5_defuzzification": {
"methods": {
"centroid": {"value": 59.24, ...},
"first_of_maxima": {"value": 34.00, ...},
"mean_of_maxima": {"value": 50.00, ...}
}
}
}Entrées :
-
Distance (D) : [0, 100] mètres
- C (Proche) : [0, 0, 40]
- M (Moyen) : [20, 50, 80]
- L (Lointain) : [60, 100, 100]
-
Vitesse Relative (ΔV) : [0, 100] km/h
- F (Faible) : [0, 0, 40]
- M (Moyen) : [20, 50, 80]
- E (Élevé) : [60, 100, 100]
Sortie :
- Pression (P) : [0, 100]
- F (Faible) : [0, 10, 35]
- M (Moyen) : [25, 50, 75]
- E (Élevé) : [60, 85, 95]
- U (Urgent) : [80, 100, 100]
Le système utilise 9 règles organisées en matrice 3×3 :
| Distance \ Vitesse | Faible | Moyen | Élevé |
|---|---|---|---|
| Lointaine | F | F | M |
| Moyenne | F | M | E |
| Proche | M | E | U |
-
Backend :
- Flask : Framework web Python
- scikit-fuzzy : Bibliothèque de logique floue
- NumPy : Calculs numériques
-
Frontend :
- Bootstrap 5 : Framework CSS
- Plotly.js : Visualisations interactives
- JavaScript vanilla : Logique frontend
Backend (Python) :
- app.py - Serveur Flask et routes API REST
- fuzzy_controller.py - Contrôleur flou avec inférence de Mamdani
- requirements.txt - Dépendances Python
Frontend :
- templates/index.html - Interface utilisateur (layout 2 colonnes)
- static/js/main.js - Logique JavaScript (auto-inférence, visualisations)
- static/css/style.css - Styles personnalisés
Tests :
- test_inference.py - Script de test pour le processus d'inférence
Les règles sont définies dans fuzzy_controller.py:57-65.
Exemple : Ajouter une nouvelle règle
# Dans la classe AEBFuzzyController
self.regles = [
ctrl.Rule(self.Distance['C'] & self.Velocity['F'], self.Pressure['M']),
ctrl.Rule(self.Distance['C'] & self.Velocity['M'], self.Pressure['E']),
# ... autres règles
ctrl.Rule(self.Distance['L'] & self.Velocity['E'], self.Pressure['M']), # NOUVELLE RÈGLE
]
# Mettre à jour les définitions pour l'affichage
self.rule_definitions = [
{"id": "R1", "distance_term": "C", "velocity_term": "F", "output_term": "M", "description": "..."},
# ... autres définitions
]Dans fuzzy_controller.py:30-50 :
# Modifier les paramètres des fonctions triangulaires
self.Distance['C'] = fuzz.trimf(self.Distance.universe, [0, 0, 50]) # Élargir "Proche"
self.Distance['M'] = fuzz.trimf(self.Distance.universe, [30, 60, 90]) # Ajuster "Moyen"






