a3-algorithmique-avancee/Projet_algo.ipynb
2023-06-07 16:50:05 +02:00

221 KiB

Projet algorithme

Contexte du projet:

L'ADEME a lancé un appel à manifestation d'intérêt pour développer des solutions de mobilité adaptées à différents territoires. CesiCDP, en collaboration avec des partenaires, s'est spécialisé dans la Mobilité Multimodale Intelligente. Dans le cadre de cet appel, l'équipe de CesiCDP travaille sur la gestion de tournées de livraison pour minimiser les trajets et optimiser l'efficacité. L'objectif est d'obtenir de nouveaux marchés et des financements pour poursuivre le développement de l'entreprise. Des contraintes supplémentaires seront ajoutées pour rendre le problème plus réaliste et intéressant pour l'ADEME.

Notre but est de réaliser un algorithme permettant de passer par tous les points de livraisons avec un temps optimisé.

Contrainte :

Les contraintes que nous avons choisi sont les suivantes:

  • Avoir plusieurs camions disponible simultanément pour effectuer les livraisons.
  • Fenêtre de temps de livraison pour chaque object

Formulation du problème

Le problème que nous avons avec les contraintes ci-dessus est le problème VRPTW (Vehicule Routing Problem avec la contrainte supplémentaire de Time Window).

  • Tous les clients doivent être déservis
  • Un client ne peut être servi que par un et un seul véhicule.
  • En quittant un client, un véhicule peut aller que vers un seul autre client.
  • Un véhicule ne peut servir un client que s'il a assez de capacité pour servir le client.

On va donc affecter chaque client à une tournée effectué par un seul véhicule.

e.

Objectifs

L'objectif de notre algorithme est de calculer et de fournir les meilleurs itinéraires pour pouvoir livrer tout les clients avec le nombre de camion disponible.

Il est possible de minimiser le nombre de camion nécessaire en optimisant les trajets et leurs temps. Il faut donc pour cela minimiser le temps de chaque parcours.

Modélisation mathématique

Nous allons représenter notre problème par un graphe
$G=(V,E)$

$V$ représente les sommets du graphe qui correspondent aux clients

$E$ représente les arcs entre deux clients $i,j \in V$

On a un ensemble $C=\{1,2,...,n_c\}$ de clients qui doivent obtenir leur livraison qui provient du dépot.

L'ensemble des emplacements des clients est défini comme : $V = C \cup \{0, n_c+1\} $

$0$ et $n_c+1$ représente le dépot, puisqu'on doit revenir au point de départ le dépots est le dernier client plus un.

On a un ensemble $V=\{1,2,...,n_v\}$ de véhicule disponible et chaque véhicule possède une capacité $Q$.

Variables de décisions:

Variables de décision:

  • Un ensemble de k véhicule

Initialisation de la matrice

In [74]:
import random
import networkx as nx
import matplotlib.pyplot as plt
import numpy as np

def generate_graph(num_nodes):
    # Créer un graphe vide
    G = nx.Graph()
    # Ajouter les sommets au graphe
    G.add_nodes_from(range(1, num_nodes + 1))
    # Ajouter au moins deux arêtes par sommet
    for node in G.nodes():
        connected_nodes = sorted(set(G.nodes()) - {node})  # Exclure le sommet lui-même et trier les nœuds
        distance1 = random.randint(1,10) 
        distance2 = random.randint(1,10)
        random_nodes = random.sample(connected_nodes, 2)
        G.add_edges_from([(node, random_nodes[0], {'distance': distance1}), 
                          (node, random_nodes[1], {'distance': distance2})])
    # Ajouter des arêtes supplémentaires de manière aléatoire
    while not nx.is_connected(G):
        node1, node2 = random.sample(G.nodes(), 2)
        if not G.has_edge(node1, node2):
            distance = random.randint(1,10)
            G.add_edge(node1, node2, distance=distance)
    return G

graph = generate_graph(100)
A = nx.adjacency_matrix(graph).todense()




print(A)

# Dessiner le graphe
nx.draw(graph, with_labels=True)
plt.show()
[[0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 ...
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]
 [0 0 0 ... 0 0 0]]
No description has been provided for this image

Définition des données du problème VRPTW

In [75]:
#k Nombre de camion disponible
k = 4

#Q capacité de marchandise de chaque camion
Q = 10

# Implémentez les étapes 1 à 6 de l'algorithme ACO ici

# 1. Définir les données du problème VRPTW
# Votre code pour attribuer les demandes et les fenêtres de temps aux clients
# Attribuer des fenêtres de temps aux clients
def assign_time_windows(graph):
    # Créer un dictionnaire pour stocker les fenêtres de temps des clients
    time_windows = {}

    # Définir la fenêtre de temps pour le dépôt central (nœud 0)
    time_windows[0] = (0, float('inf'))

    # Assigner une fenêtre de temps à chaque client
    for node in graph.nodes():
        if node !=0 and node !=100 :
            # Générer une fenêtre de temps aléatoire pour chaque client
            start_time = random.randint(0, 100)
            end_time = start_time + random.randint(10, 50)
            time_windows[node] = (start_time, end_time)

    return time_windows

# Attribuer les fenêtres de temps aux clients
time_windows = assign_time_windows(graph)

print(max(graph.nodes()))
# Afficher les fenêtres de temps assignées
for node, window in time_windows.items():
    print("Client", node, ":", window)
    

    



# 3. Initialiser la matrice de phéromones
# Votre code pour initialiser la matrice de phéromones

# 4. Implémenter la fonction de construction de la solution par une fourmi
# V
100
Client 0 : (0, inf)
Client 1 : (59, 97)
Client 2 : (22, 36)
Client 3 : (100, 131)
Client 4 : (63, 113)
Client 5 : (8, 34)
Client 6 : (73, 98)
Client 7 : (47, 96)
Client 8 : (58, 103)
Client 9 : (75, 117)
Client 10 : (43, 89)
Client 11 : (92, 136)
Client 12 : (7, 48)
Client 13 : (82, 115)
Client 14 : (37, 58)
Client 15 : (99, 132)
Client 16 : (8, 34)
Client 17 : (30, 77)
Client 18 : (26, 62)
Client 19 : (47, 73)
Client 20 : (41, 67)
Client 21 : (41, 87)
Client 22 : (3, 19)
Client 23 : (84, 126)
Client 24 : (18, 53)
Client 25 : (30, 50)
Client 26 : (57, 94)
Client 27 : (49, 77)
Client 28 : (56, 101)
Client 29 : (26, 54)
Client 30 : (66, 81)
Client 31 : (44, 63)
Client 32 : (37, 80)
Client 33 : (80, 103)
Client 34 : (5, 38)
Client 35 : (68, 79)
Client 36 : (18, 49)
Client 37 : (41, 82)
Client 38 : (55, 79)
Client 39 : (14, 47)
Client 40 : (10, 20)
Client 41 : (37, 81)
Client 42 : (29, 61)
Client 43 : (84, 125)
Client 44 : (49, 96)
Client 45 : (25, 50)
Client 46 : (81, 91)
Client 47 : (24, 40)
Client 48 : (13, 51)
Client 49 : (23, 58)
Client 50 : (86, 107)
Client 51 : (72, 117)
Client 52 : (29, 55)
Client 53 : (61, 101)
Client 54 : (36, 47)
Client 55 : (19, 34)
Client 56 : (38, 85)
Client 57 : (44, 73)
Client 58 : (79, 108)
Client 59 : (44, 74)
Client 60 : (18, 42)
Client 61 : (66, 83)
Client 62 : (99, 140)
Client 63 : (33, 66)
Client 64 : (99, 113)
Client 65 : (37, 86)
Client 66 : (71, 105)
Client 67 : (67, 102)
Client 68 : (80, 120)
Client 69 : (93, 113)
Client 70 : (56, 99)
Client 71 : (43, 75)
Client 72 : (76, 104)
Client 73 : (48, 83)
Client 74 : (34, 72)
Client 75 : (11, 24)
Client 76 : (43, 81)
Client 77 : (48, 65)
Client 78 : (26, 50)
Client 79 : (51, 62)
Client 80 : (33, 78)
Client 81 : (44, 90)
Client 82 : (86, 121)
Client 83 : (78, 115)
Client 84 : (44, 59)
Client 85 : (93, 104)
Client 86 : (62, 88)
Client 87 : (99, 145)
Client 88 : (4, 46)
Client 89 : (67, 94)
Client 90 : (17, 61)
Client 91 : (45, 95)
Client 92 : (45, 95)
Client 93 : (83, 100)
Client 94 : (30, 50)
Client 95 : (4, 28)
Client 96 : (100, 146)
Client 97 : (35, 58)
Client 98 : (18, 31)
Client 99 : (41, 57)

Définition de l'algorithme des fourmis

In [69]:
# 2. Définir les paramètres de l'algorithme ACO
num_ants = 10  # Nombre de fourmis
num_iterations = 100  # Nombre d'itérations

# 3. Initialiser la matrice de phéromones
pheromones = 0.1 * np.ones((num_nodes, num_nodes))  # Matrice de phéromones initiale

# 4. Implémenter la fonction de construction de la solution par une fourmi
def construct_solution(pheromones, visibility, demands, time_windows):
    solutions = []
    costs = []
    for ant in range(num_ants):
        current_node = 0  # Dépôt central
        unvisited_nodes = set(range(1, num_nodes))  # Tous les clients non visités
        path = [current_node]
        current_time = 0
        current_capacity = Q

        # Construction du chemin de la fourmi
        while unvisited_nodes:
            feasible_nodes = []
            feasible_probabilities = []
            for next_node in unvisited_nodes:
                if current_node != 0 and next_node != 0 and 'distance' in graph.edges[current_node, next_node]:
                    if current_time + graph.edges[current_node, next_node]['distance'] <= time_windows[next_node][1] and \
                            current_capacity >= demands[next_node]:
                        feasible_nodes.append(next_node)
                        probability = pheromones[current_node][next_node] ** pheromone_importance \
                                      * visibility[current_node][next_node] ** distance_importance
                        feasible_probabilities.append(probability)
            if feasible_nodes:
                feasible_probabilities = np.array(feasible_probabilities)
                feasible_probabilities /= np.sum(feasible_probabilities)
                next_node = random.choices(feasible_nodes, weights=feasible_probabilities)[0]
                current_node = next_node
                path.append(next_node)
                unvisited_nodes.remove(next_node)
                current_time += graph.edges[current_node, next_node]['distance']
                current_capacity -= demands[next_node]
            else:
                break

        # Ajout du retour au dépôt central
        path.append(0)

        # Calcul du coût du chemin
        cost = sum(A[path[i]][path[i + 1]] for i in range(len(path) - 1))

        solutions.append(path)
        costs.append(cost)

    return solutions, costs

# 5. Mettre à jour les phéromones
def update_pheromones(pheromones, solutions, costs):
    for solution, cost in zip(solutions, costs):
        for i in range(len(solution) - 1):
            current_node = solution[i]
            next_node = solution[i + 1]
            if cost != 0:
                pheromones[current_node][next_node] += 1 / cost
    return pheromones

# 6. Répéter les étapes 4 et 5 pour un nombre donné d'itérations
best_solution = None
best_cost = float('inf')

for iteration in range(num_iterations):
    # Construction des solutions par les fourmis
    solutions, costs = construct_solution(pheromones, visibility, demands, time_windows)

    # Mise à jour des phéromones
    pheromones = update_pheromones(pheromones, solutions, costs)

    # Recherche de la meilleure solution
    best_index = np.argmin(costs)
    if costs[best_index] < best_cost:
        best_solution = solutions[best_index]
        best_cost = costs[best_index]

# Affichage de la meilleure solution trouvée
print("Meilleure solution :", best_solution)
print("Coût de la meilleure solution :", best_cost)
Meilleure solution : [0, 0]
Coût de la meilleure solution : 0
In [ ]:
import random
import numpy as np

# Fonction d'évaluation de la qualité d'une solution (ici, la distance totale)
def evaluate_solution(solution, distances):
    total_distance = 0
    num_nodes = len(solution)

    for i in range(num_nodes - 1):
        current_node = solution[i]
        next_node = solution[i + 1]
        total_distance += distances[current_node][next_node]

    # Ajouter la distance de retour au dépôt
    total_distance += distances[solution[-1]][solution[0]]

    return total_distance

    
# Algorithme ACO
def ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta):
    num_nodes = len(distances)
    pheromone = np.ones((num_nodes, num_nodes))  # Matrice de phéromones initiale
    best_solution = None
    best_distance = float('inf')

    for iteration in range(num_iterations):
        # Construction de solutions par les fourmis
        solutions = []

        for ant in range(num_ants):
            visited = set()
            current_node = random.randint(0, num_nodes - 1)
            visited.add(current_node)
            solution = [current_node]

            while len(visited) < num_nodes:
                next_node = None
                probabilities = []

                # Calcul des probabilités de choisir chaque prochain nœud
                for node in range(num_nodes):
                    if node not in visited:
                        pheromone_value = pheromone[current_node][node]
                        distance_value = distances[current_node][node]
                        probability = (pheromone_value ** alpha) * ((1 / distance_value) ** beta)
                        probabilities.append((node, probability))

                total_probability = sum(prob for _, prob in probabilities)
                probabilities = [(node, prob / total_probability) for node, prob in probabilities]

                # Choix du prochain nœud basé sur les probabilités
                roulette_wheel = random.random()
                probability_sum = 0

                for node, probability in probabilities:
                    probability_sum += probability
                    if probability_sum >= roulette_wheel:
                        next_node = node
                        break

                visited.add(next_node)
                solution.append(next_node)
                current_node = next_node

            solutions.append(solution)

        # Évaluation des solutions et mise à jour de la meilleure solution
        for solution in solutions:
            distance = evaluate_solution(solution, distances)
            if distance < best_distance:
                best_solution = solution
                best_distance = distance

        # Mise à jour des phéromones
        pheromone *= evaporation_rate  # Évaporation des phéromones existantes

        for solution in solutions:
            delta_pheromone = 1 / evaluate_solution(solution, distances)
            for i in range(num_nodes - 1):
                node1 = solution[i]
                node2 = solution[i + 1]
                pheromone[node1][node2] += delta_pheromone
                pheromone[node2][node1] += delta_pheromone

    return best_solution, best_distance

# Exemple d'utilisation
distances = [[0, 2, 9, 10],
             [2, 0, 6, 4],
             [9, 6, 0, 8],
             [10, 4, 8, 0]]

num_ants = 10
num_iterations = 100
evaporation_rate = 0.5
alpha = 1
beta = 1

best_solution, best_distance = ant_colony_optimization(distances, num_ants, num_iterations, evaporation_rate, alpha, beta)

print("Meilleure solution :", best_solution)
print("Distance totale :", best_distance)