/// <summary> /// Génère une solution vierge /// </summary> /// <returns>Solution vierge</returns> private CVRPSolution generateFirstSolution() { CVRPSolution sol = (CVRPSolution)_initialSol.copy(); //On copie la solution initiale sol.reset(data); //On réinitialise la solution sol.data = data; sol.addNewRoute(data); //On ajoute une nouvelle route return(sol); }
/// <summary> /// Démarre la résolution /// </summary> public void solve() { majValue = getFixedCost(_initialSol); //On détermine le coût de la solution initiale bestSol = _initialSol; //On la définit comme meilleure solution TreeNode tr = new TreeNode(generateFirstSolution(), null); //On initialise l'arbre de recherche TreeNode result = solve(tr, 0); //On démarre la résolution logger.log(computedSteps + " calculs ont étés réalisés", Message.Level.Information); OnSolutionFound(new SolutionFoundEventArgs(bestSol)); //On déclenche l'évenement correspondant à la résolution }
/// <summary> /// Retourne une solution initialisée /// </summary> /// <param name="parser">DataPArser à utiliser</param> /// <returns>Solution initialisée</returns> private static CVRPSolution _AddVars(DataParser parser) { CVRPSolution sol = new CVRPSolution(parser); //On créé une nouvelle instance de la solution //On parcours les variables for (int i = 0; i < parser.nodeList.Count; i++) { sol.addVariable(new Variable(i)); //On ajoute la variable à la solution } return(sol); }
/// <summary> /// Retourne la distance associée aux variables fixées de sol /// </summary> /// <param name="sol">Solution</param> /// <returns>Distance</returns> private double getFixedCost(CVRPSolution sol) { double total = 0; //On somme les coûts de chaques routes de la solution foreach (Route r in sol.routes) { total += getRouteCost(r); } return(total); }
/// <summary> /// Retourne la borne inférieure de la solution par le sismplex /// </summary> /// <param name="sol">Solution</param> /// <returns>Borne inférieure</returns> private double evaluateSolutionSimplRelax(CVRPSolution sol) { double total = getFixedCost(sol); //On calule la distance fixée if (sol.freeVars.Count > 1) { BoundSimplex rbTSPSolver = new BoundSimplex(data, logger, sol); //On initilialise le solveur simplex total += rbTSPSolver.solve(); //On détermine la borne inférieure des variables libres } return(total); }
/// <summary> /// Met à jour le vecteur des coûts minimaux de la solution s /// </summary> /// <param name="s"></param> private void updateMinCost(CVRPSolution s) { //On bloque la dernière variable devenue intérieure à une route if (s.varsIdList.Count > 2) { for (int i = 0; i < s._minCost.Length; i++) { s._minCost[i][s.varsIdList[s.varsIdList.Count - 2]] = 0; } } }
/// <summary> /// Retourne une solution par ordre d'apparition dans le fichier /// </summary> /// <param name="parser">DataParser à utiliser</param> /// <param name="loadConstraint">Contrainte de chargement</param> /// <returns>Solution générée</returns> public static CVRPSolution OrderedSolution(DataParser parser, double loadConstraint) { CVRPSolution sol = _AddVars(parser); //Génère une instance de la solution sol.addNewRoute(parser); //Créé une nouvelle route //On fixe les variables for (int i = 0; i < parser.nodeList.Count; i++) { sol.setVariable(i, parser, loadConstraint); } sol.endSolution(); //On termine la solution return(sol); }
/// <summary> /// Retourne la borne inférieure de la solution par méthode hybride /// </summary> /// <param name="sol">Solution</param> /// <param name="step">Profondeur d'hybridation</param> /// <returns></returns> private double evaluateSolutionHybrid(CVRPSolution sol, int step) { //Si la profondeur dépasse la profondeur d'hybridation if (step >= methodSwap) { updateMinCost(sol); //On met à jour le vecteur des coûts minimaux return(evaluateSolutionSimplRelax(sol)); //On utilise le simplex } else { return(evaluateSolutionBasic(sol)); //On utilise la méthode du PPVE } }
//Constructeur public BnBSolverCVRP(Problem pb, CVRPSolution initialSolution, DataParser data, Logger logger, int evalMethod, double loadConstraint, int methodSwap = 0, bool benchmark = false) { //Attention à donner une solution avec toutes les variables fixées. //On copie les arguments _pb = pb; this.logger = logger; this.data = data; computeMinCost(); //Calcul le vecteur des coûts minimaux _initialSol = initialSolution; _initialSol._minCost = this._minCost; this.evalMethod = evalMethod; this.loadConstraint = loadConstraint; this.methodSwap = methodSwap; this.benchmark = benchmark; }
/// <summary> /// Retourne la borne inférieure de la solution par la méthode du PPVE /// </summary> /// <param name="solution">Solution</param> /// <returns>Borne inférieure</returns> private double evaluateSolutionBasic(CVRPSolution solution) { double total = getFixedCost(solution); //On calule la distance fixée updateMinCost(solution); //On met à jour le vecteur des coûts minimaux //On somme les distances minimales des variables libres foreach (int i in solution.freeVars) { total += getMinCost(solution, i); } if (solution.varsIdList.Count >= 1 && solution.freeVars.Count >= 1) { total += getMinCost(solution, solution.varsIdList[solution.varsIdList.Count - 1]); } return(total); }
/// <summary> /// Détermine la borne inférieure de s /// </summary> /// <param name="sol">Solution</param> /// <param name="step">Profondeur de parcours</param> /// <returns>Borne inférieure</returns> private double evaluateSolution(CVRPSolution sol, int step) { switch (evalMethod) { case 0: return(evaluateSolutionBasic(sol)); //Méthode PPVE case 1: return(evaluateSolutionSimplRelax(sol)); //Méthode simplex case 2: return(evaluateSolutionHybrid(sol, step)); //Méthode hybride default: throw new NotImplementedException(); } }
/// <summary> /// Retourne le coût minimum pour varId dans s /// </summary> /// <param name="s">Solution</param> /// <param name="varId">Variable dont on charche la distance minimale</param> /// <returns>Distance minimale dans s</returns> private double getMinCost(CVRPSolution s, int varId) { double min = 0; for (int i = 0; i < s._minCost[varId].Count; i++) { if (s._minCost[varId][i] != 0) { if (min == 0) { min = s._minCost[varId][i]; } else if (min > s._minCost[varId][i]) { min = s._minCost[varId][i]; } } } return(min); }
/// <summary> /// Retourne une solution par la méthode des plus prches voisins /// </summary> /// <param name="parser">DataParser à utiliser</param> /// <param name="loadConstraint">Contrainte de chargement</param> /// <returns>Solution générée</returns> public static CVRPSolution PPVSolution(DataParser parser, double loadConstraint) { CVRPSolution sol = _AddVars(parser); //Génère une instance de la solution sol.addNewRoute(parser); //Créé une nouvelle route sol.setVariable(0, parser, loadConstraint); //Fixe le dépot dans la route double[,] arcWeight = (double[, ])parser.arcWeight.Clone(); //Créé une copie de la matrice des distance //On boucle tant que tout les clients ne sont pas servis while (sol.freeVarCount != 0) { int nearestId = 0; //On initialise le plus proche voisin double distance = arcWeight[sol.varsIdList[sol.varsIdList.Count - 1], 0]; //On initialise sa distance au dernier point visité //On parcours tout les voisins potentiels for (int j = 1; j < parser.nodeList.Count; j++) { //Si l'arc vers j est réalisable et meilleur que les précédents considérés if (arcWeight[sol.varsIdList[sol.varsIdList.Count - 1], j] != 0 && (distance == 0 || arcWeight[sol.varsIdList[sol.varsIdList.Count - 1], j] < distance)) { //On met à jour le plus prche voisin nearestId = j; distance = arcWeight[sol.varsIdList[sol.varsIdList.Count - 1], j]; } } //Si on revient au dépot, on créé une nouvelle solution if (nearestId == 0) { sol.endRoute(); } else { sol.setVariable(nearestId, parser, loadConstraint); //On fixe le plus proche voisin } //On empêche le plus proche voisin d'être de nouveau disponible for (int j = 0; j < parser.nodeList.Count; j++) { arcWeight[j, nearestId] = 0; } } sol.endSolution(); //On termine la solution return(sol); }
/// <summary> /// Déclenche l'évenement d'affichage /// </summary> /// <param name="s">SSolution à afficher</param> public void displaySolution(CVRPSolution s) { OnPartialSolutionFound(new SolutionFoundEventArgs(s)); }
public SolutionFoundEventArgs(CVRPSolution res) { result = res; }
/// <summary> /// Retourne une solution par la méthode de Clarke and Wright /// </summary> /// <param name="parser">DataParser à utiliser</param> /// <param name="loadConstraint">Contrainte de chargement</param> /// <returns>Solution générée</returns> public static CVRPSolution ClarkeWrightSolution(DataParser parser, double loadConstraint) { CVRPSolution sol = _AddVars(parser); //Génère une instance de la solution //On initialise la liste des savings List <KeyValuePair <Tuple <int, int>, double> > savings = new List <KeyValuePair <Tuple <int, int>, double> >(); int j; //On génère les savings for (int i = 1; i < parser.nodeList.Count; i++) { for (j = 1; j < parser.nodeList.Count - 1; j++) { int realJ = j; //On évite l'arc i->i if (j >= i) { realJ++; } savings.Add(new KeyValuePair <Tuple <int, int>, double>(new Tuple <int, int>(i, realJ), parser.arcWeight[0, realJ] + parser.arcWeight[i, 0] - parser.arcWeight[i, realJ])); } } //On trie la liste savings.Sort( delegate(KeyValuePair <Tuple <int, int>, double> pair1, KeyValuePair <Tuple <int, int>, double> pair2) { return(pair1.Value.CompareTo(pair2.Value)); }); savings.Reverse(); //On la renverse (ordre décroissant) List <List <int> > routes = new List <List <int> >(); //Liste des routes de la solution List <double> routeDist = new List <double>(); //Liste des distances associés à chaques routes List <double> routeCosts = new List <double>(); //Liste des coûts associés à chaques routes //On initialise les routes initiales for (int i = 1; i < parser.nodeList.Count; i++) { routes.Add(new List <int>(new int[] { i })); routeDist.Add(parser.arcWeight[0, i] + parser.arcWeight[i, 0]); routeCosts.Add(parser.nodeList[i].quantity); } j = 0; //Tant que la solution while (j < savings.Count - 1 && savings[j].Value >= 0) { Tuple <int, int> couple = savings[j].Key; //On récupère les noeuds associés au saving //On vérifie que les deux noeuds peuvent être fusionnés object[] res = _areMergeable(couple.Item1, couple.Item2, routes, routeDist, routeCosts, savings[j].Value, loadConstraint); //Si ils peuvent être fusionné if ((bool)res[0]) { routes[(int)res[1]] = routes[(int)res[1]].Concat(routes[(int)res[2]]).ToList(); //On créé la nouvelle route routeDist[(int)res[1]] = (double)res[4]; //On met à jour la distance de la route routeCosts[(int)res[1]] = (double)res[3]; //On met à jour le coût de la route routes.RemoveAt((int)res[2]); //On suppprime l'ancienne route routeDist.RemoveAt((int)res[2]); routeCosts.RemoveAt((int)res[2]); //On supprime le coût associé } j++; } //On créé la solution foreach (List <int> r in routes) { sol.addNewRoute(parser); foreach (int node in r) { sol.setVariable(node, parser, loadConstraint); } sol.endSolution(); } return(sol); }
/// <summary> /// Fonction récursive de résolution /// </summary> /// <param name="currentNode">Noeud courant de l'arbre</param> /// <param name="step">Profondeur du parcours</param> /// <returns>Noeud courant</returns> private TreeNode solve(TreeNode currentNode, int step) { //Si la solution est une solution finale if (currentNode.sol.isFinalSolution()) { //On al déclare comme finale ((CVRPSolution)currentNode.sol).endSolution(); currentNode.sol.eval = getFixedCost((CVRPSolution)currentNode.sol); //Si cette solution est meilleure if (majValue > currentNode.sol.eval) { //On met à jour la meilleure solution et on l'affiche majValue = currentNode.sol.eval; bestSol = (CVRPSolution)currentNode.sol; logger.log("Meilleur solution trouvée de coût:" + ((int)Math.Round(currentNode.sol.eval)).ToString(), Message.Level.Warning); computedSteps++; displaySolution(bestSol); } else { currentNode.isLocked = true; //On bloque la solution computedSteps++; } updateBenchmark(step); return(currentNode); } else { List <TreeNode> nextSol = new List <TreeNode>(); //On initialise la liste des enfants for (int i = 0; i < _pb.varCount; i++) { //Pour chaque variable en encors libre if (!currentNode.sol.isFixed(i)) { CVRPSolution nSol1 = (CVRPSolution)currentNode.sol.copy(); //On copie la solution nSol1.setVariable(i, data, loadConstraint); //On fixe une nouvelle variable nSol1.eval = evaluateSolution(nSol1, step); //On détermine la borne inférieure //Si la solution est viable //> ou < selon minimisation ou maximisation if (nSol1.eval <= majValue) { nextSol.Add(new TreeNode(nSol1, null)); //On l'ajoute aux enfants } else { computedSteps++; } //Si on peut obtenir une autre solution en créant une nouvelle route if (nSol1.routes.Count == ((CVRPSolution)currentNode.sol).routes.Count) { CVRPSolution nSol2 = (CVRPSolution)currentNode.sol.copy(); nSol2.endRoute(); nSol2.setVariable(i, data, loadConstraint); nSol2.eval = evaluateSolution(nSol2, step); //> ou < selon minimisation ou maximisation if (nSol2.eval <= majValue) { nextSol.Add(new TreeNode(nSol2, null)); } else { computedSteps++; } } } } //Si il y a des enfants if (nextSol.Count != 0) { nextSol.Sort((x, y) => x.sol.eval.CompareTo(y.sol.eval)); //On les trie selon leur borne inférieure //On parcours les enfants for (int i = 0; i < nextSol.Count; i++) { //Si la solution est toujours viable if (nextSol[i].sol.eval <= majValue) { nextSol[i] = solve(nextSol[i], step + 1); //On poursuit la résolution } else { nextSol[i].isLocked = true; computedSteps++; } } currentNode.childs = nextSol; } else { currentNode.isLocked = true; updateBenchmark(step); } computedSteps++; int locked = 0; //On regarde si tout les enfants sont bloquéss foreach (TreeNode node in nextSol) { if (node.isLocked) { node.Dispose(); locked++; } } //Si ils le sont tous, on bloque la solution if (locked == nextSol.Count) { currentNode.isLocked = true; nextSol = null; } return(currentNode); } }