private static PathSolutionScript CrossOver(PathSolutionScript p1, PathSolutionScript p2) { var child = p1.Actions.Length > p2.Actions.Length ? new PathSolutionScript(p1.Actions.Length) : new PathSolutionScript(p2.Actions.Length); for (var i = 0; i < child.Actions.Length; i++) { if (i < p1.Actions.Length && i % 2 == 0) { child.Actions[i] = p1.Actions[i]; } else if (i < p2.Actions.Length && i % 2 != 0) { child.Actions[i] = p2.Actions[i]; } else if (i > p1.Actions.Length) { child.Actions[i] = p2.Actions[i]; } else if (i > p2.Actions.Length) { child.Actions[i] = p1.Actions[i]; } } return(child); }
public void LaunchSimulation(PathSolutionScript solution) { InSimulation = true; _initialPosition = transform.position; this.GetComponent <TrailRenderer>().time = _totalAnimationTime; _solution = solution; Initialize(); StartCoroutine("PlaySolution", _totalAnimationTime); }
public PathSolutionScript CopySolution(PathSolutionScript sol) { var newSol = new PathSolutionScript(_totalSolutionSteps); for (int i = 0; i < sol.Actions.Length; i++) { newSol.Actions[i].Action = sol.Actions[i].Action; } return(newSol); }
/// <summary> /// Fonction utilitaire ayant pour but de copier /// dans un nouvel espace m�moire une solution /// </summary> /// <param name="sol">La solution � copier</param> /// <returns>Une copie de la solution</returns> public PathSolutionScript CopySolution(PathSolutionScript sol) { // Initialisation de la nouvelle s�quence d'action // de la m�me longueur que celle que l'on souhaite copier var newSol = new PathSolutionScript(sol.Actions.Length); // Pour chaque action de la s�quence originale, // on copie le type d'action. for (int i = 0; i < sol.Actions.Length; i++) { newSol.Actions[i].Action = sol.Actions[i].Action; } // Renvoi de la solution copi�e return(newSol); }
public IEnumerator NaiveLocalSearch() { _isRunning = true; var currentSolution = new PathSolutionScript(42); var scoreEnumerator = GetError(currentSolution); yield return(StartCoroutine(scoreEnumerator)); float currentError = scoreEnumerator.Current; var minimumError = GetMinError(); Debug.Log(currentError); int iterations = 0; while (currentError != minimumError) { var newsolution = CopySolution(currentSolution); RandomChangeInSolution(newsolution); var newscoreEnumerator = GetError(newsolution); yield return(StartCoroutine(newscoreEnumerator)); float newError = newscoreEnumerator.Current; Debug.Log(currentError + " - " + newError); if (newError <= currentError) { currentSolution = newsolution; currentError = newError; } iterations++; yield return(null); } _isRunning = false; Debug.Log("CONGRATULATIONS !!! Solution Found in " + iterations + " iterations !"); }
/// <summary> /// Exemple d'oracle nous renvoyant un score que l'on essaye de minimiser /// Ici est utilis� la position de la case d'arriv�e, la position finale /// atteinte par la solution. Il est recommand� d'essayer plusieurs oracles /// pour �tudier le comportement des algorithmes selon la qualit� de ces /// derniers /// /// Parmi les param�tres pouvant �tre utilis�s pour calculer le score/erreur : /// /// - position de la case d'arriv�e : PlayerScript.GoalXPositionInMatrix /// PlayerScript.GoalYPositionInMatrix /// - position du joueur : player.PlayerXPositionInMatrix /// player.PlayerYPositionInMatrix /// - position de d�part du joueur : PlayerScript.StartXPositionInMatrix /// PlayerScript.StartYPositionInMatrix /// - nombre de cases explor�es : player.ExploredPuts /// - nombre d'actions ex�cut�es : player.PerformedActionsNumber /// - vrai si le la balle a touch� la case d'arriv�e : player.FoundGoal /// - vrai si le la balle a touch� un obstacle : player.FoundObstacle /// - interrogation de la matrice : /// � la case de coordonn�e (i, j) est elle un obstacle (i et j entre 0 et 49) : /// player.GetPutTypeAtCoordinates(i, j) == LayerMask.NameToLayer("Obstacle") /// � la case de coordonn�e (i, j) est elle explor�e (i et j entre 0 et 49) : /// player.GetPutTypeAtCoordinates(i, j) == 1 /// � la case de coordonn�e (i, j) est elle inexplor�e (i et j entre 0 et 49) : /// player.GetPutTypeAtCoordinates(i, j) == 0 /// </summary> /// <param name="solution"></param> /// <returns></returns> IEnumerator <float> GetError(PathSolutionScript solution) { // On indique que l'on s'appr�te � lancer la simulation _inSimulation = true; // On cr�� notre objet que va ex�cuter notre s�quence d'action var player = PlayerScript.CreatePlayer(); // Pour pouvoir visualiser la simulation (moins rapide) player.RunWithoutSimulation = true; // On lance la simulation en sp�cifiant // la s�quence d'action � ex�cuter player.LaunchSimulation(solution); // Tout pendant que la simulation n'est pas termin�e while (player.InSimulation) { // On rend la main au moteur Unity3D yield return(-1f); } // Calcule la distance de Manhattan entre la case d'arriv�e et la case finale de // notre objet, la pond�re (la multiplie par z�ro si le but a �t� trouv�) // et ajoute le nombre d'actions jou�es var error = (Mathf.Abs(PlayerScript.GoalXPositionInMatrix - player.PlayerXPositionInMatrix) + Mathf.Abs(PlayerScript.GoalYPositionInMatrix - player.PlayerYPositionInMatrix)) * (player.FoundGoal ? 0 : 100) + player.PerformedActionsNumber; //Debug.Log(player.FoundGoal); // D�truit l'objet de la simulation Destroy(player.gameObject); // Renvoie l'erreur pr�c�demment calcul�e yield return(error); // Indique que la phase de simulation est termin�e _inSimulation = false; }
/// <summary> /// Execute un changement al�atoire sur une solution /// ici, une action de la s�quence est tir�e au hasard et remplac�e /// par une nouvelle au hasard. /// </summary> /// <param name="sol"></param> public void RandomChangeInSolution(PathSolutionScript sol) { sol.Actions[Random.Range(0, sol.Actions.Length)] = new ActionSolutionScript(); }
public IEnumerator SimulatedAnnealing() { _isRunning = true; var currentSolution = new PathSolutionScript(42); var scoreEnumerator = GetError(currentSolution); yield return(StartCoroutine(scoreEnumerator)); float currentError = scoreEnumerator.Current; var temperature = 0f; var stagnation = 0; var minimumError = GetMinError(); Debug.Log(currentError); int iterations = 0; while (currentError != minimumError) { float oldError = currentError; var newsolution = CopySolution(currentSolution); RandomChangeInSolution(newsolution); var newscoreEnumerator = GetError(newsolution); yield return(StartCoroutine(newscoreEnumerator)); float newError = newscoreEnumerator.Current; Debug.Log(currentError + " - " + newError); var rdm = Random.Range(0f, 1f); if (rdm <= MetropolisCriterium(currentError, newError, temperature)) { currentSolution = newsolution; currentError = newError; } if (oldError == currentError) { stagnation++; } else { stagnation = 0; } if (stagnation > 200) { temperature = 6f; stagnation = 0; } temperature *= 0.9999f; Debug.Log("Temperature: " + temperature + " Stagnation: " + stagnation); iterations++; yield return(0); } _isRunning = false; Debug.Log("CONGRATULATIONS !!! Solution Found in " + iterations + " iterations !"); }
/// <summary> /// Fonction utilitaire ayant pour but de copier /// dans un nouvel espace mémoire une solution /// </summary> /// <param name="sol">La solution à copier</param> /// <returns>Une copie de la solution</returns> public PathSolutionScript CopySolution(PathSolutionScript sol) { // Initialisation de la nouvelle séquence d'action // de la même longueur que celle que l'on souhaite copier var newSol = new PathSolutionScript(sol.Actions.Length); // Pour chaque action de la séquence originale, // on copie le type d'action. for (int i = 0; i < sol.Actions.Length; i++) { newSol.Actions[i].Action = sol.Actions[i].Action; } // Renvoi de la solution copiée return newSol; }
/// <summary> /// Exemple d'oracle nous renvoyant un score que l'on essaye de minimiser /// Ici est utilisé la position de la case d'arrivée, la position finale /// atteinte par la solution. Il est recommandé d'essayer plusieurs oracles /// pour étudier le comportement des algorithmes selon la qualité de ces /// derniers /// /// Parmi les paramètres pouvant être utilisés pour calculer le score/erreur : /// /// - position de la case d'arrivée : PlayerScript.GoalXPositionInMatrix /// PlayerScript.GoalYPositionInMatrix /// - position du joueur : player.PlayerXPositionInMatrix /// player.PlayerYPositionInMatrix /// - position de départ du joueur : PlayerScript.StartXPositionInMatrix /// PlayerScript.StartYPositionInMatrix /// - nombre de cases explorées : player.ExploredPuts /// - nombre d'actions exécutées : player.PerformedActionsNumber /// - vrai si le la balle a touché la case d'arrivée : player.FoundGoal /// - vrai si le la balle a touché un obstacle : player.FoundObstacle /// - interrogation de la matrice : /// € la case de coordonnée (i, j) est elle un obstacle (i et j entre 0 et 49) : /// player.GetPutTypeAtCoordinates(i, j) == LayerMask.NameToLayer("Obstacle") /// € la case de coordonnée (i, j) est elle explorée (i et j entre 0 et 49) : /// player.GetPutTypeAtCoordinates(i, j) == 1 /// € la case de coordonnée (i, j) est elle inexplorée (i et j entre 0 et 49) : /// player.GetPutTypeAtCoordinates(i, j) == 0 /// </summary> /// <param name="solution"></param> /// <returns></returns> IEnumerator<float> GetError(PathSolutionScript solution, System.DateTime time,int iteration) { // On indique que l'on s'apprête à lancer la simulation _inSimulation = true; // On créé notre objet que va exécuter notre séquence d'action var player = PlayerScript.CreatePlayer(); // Pour pouvoir visualiser la simulation (moins rapide) player.RunWithoutSimulation = false; // On lance la simulation en spécifiant // la séquence d'action à exécuter player.LaunchSimulation(solution); // Tout pendant que la simulation n'est pas terminée while (player.InSimulation) { // On rend la main au moteur Unity3D yield return -1f; } // Calcule la distance de Manhattan entre la case d'arrivée et la case finale de // notre objet, la pondère (la multiplie par zéro si le but a été trouvé) // et ajoute le nombre d'actions jouées var error = (Mathf.Abs(PlayerScript.GoalXPositionInMatrix - player.PlayerXPositionInMatrix) + Mathf.Abs(PlayerScript.GoalYPositionInMatrix - player.PlayerYPositionInMatrix)) * (player.FoundGoal ? 0 : 100) + player.PerformedActionsNumber; if(player.FoundGoal==true) { if(!_wasTrue){ _wasTrue = true; string localCsvLine = _csvLog; TimeSpan simulation = DateTime.Now - time; localCsvLine += "00:00:"+simulation.TotalSeconds+";"+iteration+";Correct;\n"; Debug.Log("Premier True En "+ simulation.TotalSeconds + " secondes"); File.AppendAllText(@"log.csv",localCsvLine ); } } // Détruit l'objet de la simulation Destroy(player.gameObject); // Renvoie l'erreur précédemment calculée yield return error; // Indique que la phase de simulation est terminée _inSimulation = false; }
/// <summary> /// Méthode proposant une méthode pour obtenir une nouvel /// individu par croisement de deux configurations parentes /// </summary> /// <param name="parent1">Le parent 1</param> /// <param name="parent2">Le parent 2</param> /// <returns>L'enfant généré par croisement</returns> PathSolutionScript Crossover(PathSolutionScript parent1, PathSolutionScript parent2, int length) { PathSolutionScript child = new PathSolutionScript(length); for (int i = 0; i < parent1.Actions.Count(); i++) { if(i%2 != 0)child.Actions[i].Action = parent1.Actions[i].Action; else child.Actions[i].Action = parent2.Actions[i].Action; } return child; }
// Coroutine à utiliser pour implémenter l'algorithme du recuit simulé public IEnumerator SimulatedAnnealing() { /* * * Implémentation Christophe * Implémentation du Recuit Simulé * Lire le README AVANT modification * */ DateTime time = DateTime.Now; DateTime timeEnd = time.AddMinutes(5); // Indique que l'algorithme est en cours d'exécution _isRunning = true; //Génération de la meilleure erreur obtenue var minimumError = GetMinError(); var nbPath = 42; // Génère une solution initiale au hazard (ici une séquence // de 42 mouvements) var currentSolution = new PathSolutionScript(nbPath); int iterations = 0; // Récupère le score de la solution initiale // Sachant que l'évaluation peut nécessiter une // simulation, pour pouvoir la visualiser nous // avons recours à une coroutine var scoreEnumerator = GetError(currentSolution,time,iterations); yield return StartCoroutine(scoreEnumerator); float currentError = scoreEnumerator.Current; //Initialisation de la température à une valeur 'plutot basse'. float temperature = 2f; ///Initialisation de la valeur de 'stagnation' qui si elle dépasse un ///certain seuil provoquera l'augmentation de la température. float stagnation = 0.001f; // Affichage de l'erreur initiale Debug.Log(currentError); // Initialisation du nombre d'itérations // Tout pendant que l'erreur minimale n'est pas atteinte while (currentError != minimumError && DateTime.Compare(DateTime.Now,timeEnd)<0) { // On obtient une copie de la solution courante // pour ne pas la modifier dans le cas ou la modification // ne soit pas conservée. var newsolution = CopySolution(currentSolution); // On procède à une petite modification de la solution // courante. RandomChangeInSolution(newsolution); // Récupère le score de la nouvelle solution // Sachant que l'évaluation peut nécessiter une // simulation, pour pouvoir la visualiser nous // avons recours à une coroutine var newscoreEnumerator = GetError(newsolution,time,iterations); yield return StartCoroutine(newscoreEnumerator); float newError = newscoreEnumerator.Current; // On affiche pour des raisons de Debug et de suivi // la comparaison entre l'erreur courante et la // nouvelle erreur Debug.Log(currentError + " - " + newError); Debug.Log("L'erreur min est :" + minimumError); Debug.Log("Le nombre d'itérations est de " + iterations); ///Tirage d'un nombre aléatoire entre 0f et 1f. float rdm = UnityEngine.Random.Range(0f, 1f); ///Comparaison de ce nombre à la probabilité d'accepter un changement ///déterminée par le critère de Boltzman. if (rdm < BoltzmanCriteria(temperature, currentError, newError)) { ///Si le nombre est inférieur, on accepte la solution changée ///et l'on met à jour l'erreur courante. currentError = newError; //Bien évidement la solution devient la copie de l'ancienne solution currentSolution = newsolution; } ///Si l'erreur stagne if (minimumError == currentError) { ///On incrémente la stagnation stagnation *= 1.001f; } else { ///Sinon on la réinitialise stagnation = 0.001f; } ///Si l'erreur diminue en deça de la meilleure erreur obtenue if (currentError < minimumError) { ///On met à jour la meilleure erreur obtenue minimumError = (int)currentError; ///On réinitialise la stagnation stagnation = 0.001f; } ///On met à jour la temperature à chaque tour de boucle : /// - si la stagnation est suffisante la temperature va augmenter /// - sinon la temperature décroit de manière géométrique temperature *= 0.998f + stagnation; ///Affichage dans la console de Debug du couple temperature stagnation ///pour pouvoir être témoin de l'augmentation de la température lorsque ///l'on se retrouve coincé trop longtemps dans un minimum local. Debug.Log(temperature + " - " + stagnation); ///On rend la main à Unity pendant un court laps de temps pour permettre ///le contrôle de la simulation ainsi que la visualisation de l'évolution ///de l'algorithme. yield return new WaitForSeconds(0.0001f); // On incrémente le nombre d'itérations iterations++; // On rend la main au moteur Unity3D yield return 0; } // Fin de l'algorithme, on indique que son exécution est stoppée _isRunning = false; TimeSpan simulation = DateTime.Now - time; string localCsvLine = _csvLog; if(DateTime.Compare(DateTime.Now,timeEnd)<0){ // On affiche le nombre d'itérations nécessaire à l'algorithme pour trouver la solution Debug.Log("CONGRATULATIONS !!! Solution Found in " + iterations + " iterations !"); localCsvLine += "00:00:"+simulation.TotalSeconds+";"+iterations+";Optimal;\n"; Debug.Log("En "+ simulation.TotalSeconds + " secondes !"); }else{ Debug.Log("FAIL !!! Solution not Found in " + iterations + " iteration and "+ simulation.TotalSeconds+" Seconds, or "+ simulation.TotalMinutes); localCsvLine += "00:00:"+simulation.TotalSeconds+";"+iterations+";Echec;\n"; } File.AppendAllText(@"log.csv",localCsvLine ); }
/// <summary> /// Execute un changement aléatoire sur une solution /// ici, une action de la séquence est tirée au hasard et remplacée /// par une nouvelle au hasard. /// </summary> /// <param name="sol"></param> public void RandomChangeInSolution(PathSolutionScript sol) { sol.Actions[UnityEngine.Random.Range(0, sol.Actions.Length)] = new ActionSolutionScript(); }
/// <summary> /// Implémentation possible de la recherche locale naïve /// sous forme de coroutine pour le mode pseudo asynchone /// </summary> /// <returns></returns> public IEnumerator NaiveLocalSearch() { // Indique que l'algorithme est en cours d'exécution _isRunning = true; DateTime time = DateTime.Now; // Génère une solution initiale au hazard (ici une séquence // de 42 mouvements) var currentSolution = new PathSolutionScript(42); int iterations = 0; // Récupère le score de la solution initiale // Sachant que l'évaluation peut nécessiter une // simulation, pour pouvoir la visualiser nous // avons recours à une coroutine var scoreEnumerator = GetError(currentSolution,time,iterations); yield return StartCoroutine(scoreEnumerator); float currentError = scoreEnumerator.Current; // Nous récupérons l'erreur minimum atteignable // Ceci est optionnel et dépendant de la fonction // d'erreur var minimumError = GetMinError(); // Affichage de l'erreur initiale Debug.Log(currentError); // Initialisation du nombre d'itérations // Tout pendant que l'erreur minimale n'est pas atteinte while (currentError != GetMinError()) { // On obtient une copie de la solution courante // pour ne pas la modifier dans le cas ou la modification // ne soit pas conservée. var newsolution = CopySolution(currentSolution); // On procède à une petite modification de la solution // courante. RandomChangeInSolution(newsolution); // Récupère le score de la nouvelle solution // Sachant que l'évaluation peut nécessiter une // simulation, pour pouvoir la visualiser nous // avons recours à une coroutine var newscoreEnumerator = GetError(newsolution,time,iterations); yield return StartCoroutine(newscoreEnumerator); float newError = newscoreEnumerator.Current; // On affiche pour des raisons de Debug et de suivi // la comparaison entre l'erreur courante et la // nouvelle erreur Debug.Log(currentError + " - " + newError); // Si la solution a été améliorée if (newError <= currentError) { // On met à jour la solution courante currentSolution = newsolution; // On met à jour l'erreur courante currentError = newError; } // On incrémente le nombre d'itérations iterations++; // On rend la main au moteur Unity3D yield return 0; } // Fin de l'algorithme, on indique que son exécution est stoppée _isRunning = false; // On affiche le nombre d'itérations nécessaire à l'algorithme pour trouver la solution Debug.Log("CONGRATULATIONS !!! Solution Found in " + iterations + " iterations !"); }
// Coroutine à utiliser pour implémenter un algorithme génétique public IEnumerator GeneticAlgorithm(int popsize,float mutationRate,float fitness,int mutationSequence) { int iteration = 0; DateTime time = DateTime.Now; DateTime timeEnd = time.AddMinutes(5); #region Paramètres ///La taille de la population ///Le nombre d'individus séléctionnés pour la reproduction ///(ici 40% de la taille de la population) int numSelection = (int)(popsize * fitness); ///Le taux de mutation (c.à.d. la chance avec laquelle un ///individu issu du croisement peut subir une mutation) #endregion /* * * INITIALISATION * */ ///Initialisation du tableau contenant notre population initiale PathSolutionScript[] population = new PathSolutionScript[popsize]; //Génération de la meilleure erreur obtenue var minimumError = GetMinError(); Debug.Log("Minimum Error to Win : " + minimumError); int nbPath = 42; var currentSol = new PathSolutionScript(nbPath); var sol = new PathSolutionScript(nbPath); float bestError = 10000; // Récupère le score de la solution initiale // Sachant que l'évaluation peut nécessiter une // simulation, pour pouvoir la visualiser nous // avons recours à une coroutine var scoreEnumerator = GetError(currentSol,time,iteration); yield return StartCoroutine(scoreEnumerator); float currentError = scoreEnumerator.Current; for (int i = 0; i < popsize; i++) { //On génère une solution population[i] = new PathSolutionScript(nbPath); } while (bestError != GetMinError() && DateTime.Compare(DateTime.Now,timeEnd)<0) { ///Initialisation du tableau destiné à contenir l'ensemble des ///couples chemin/erreur une fois la population évaluée var scoredIndividuals = new ScoredIndividual[popsize]; currentSol = new PathSolutionScript(nbPath); ///Pour chaque individu de notre population à évaluer for (int i = 0; i < population.Length; i++) { for (int j = 0; j < population[i].Actions.Length; j++) { currentSol.Actions[j].Action = population[i].Actions[j].Action; } scoreEnumerator = GetError(population[i],time,iteration); yield return StartCoroutine(scoreEnumerator); currentError = scoreEnumerator.Current; ///Création d'un couple configuration/solution et stockage ///du score obtenu pour la configuration évaluée. scoredIndividuals[i] = new ScoredIndividual() { Configuration = population[i], Score = currentError }; } //operateur de selection //selection par tournoi PathSolutionScript[] bestIndividuals = new PathSolutionScript[popsize]; for(int i=0;i< popsize;i++){ ////si l'individu testé est moins bon que l'aléatoire int rand = UnityEngine.Random.Range(0,scoredIndividuals.Length); if(scoredIndividuals[i].Score > scoredIndividuals[rand].Score) { bestIndividuals[i] = scoredIndividuals[rand].Configuration; }else{ bestIndividuals[i] = scoredIndividuals[i].Configuration; } // scoredIndividuals[UnityEngine.Random.Range(0,scoredIndividuals.Length)] } //On initialise notre stockage de best population //var bestIndividuals = scoredIndividuals; //Methode Roulette Wheel //.OrderBy((scoredindi) => scoredindi.Score*UnityEngine.Random.value) //methode Elitiste //.OrderBy((scoredindi) => scoredindi.Score) //.Take(numSelection) //.Select((scoredindi) => scoredindi.Configuration) //.ToArray(); bestError = scoredIndividuals .OrderBy((scoredindi) => scoredindi.Score) .Select((scoredindi) => scoredindi.Score).First(); ///Affichage Dans la console de Debug du score du meilleur ///individu. Debug.Log("Meilleur Score de la génération courante : " + bestError); ///Initialisation de la nouvelle population qui va être générée ///par croisement. PathSolutionScript[] newpopulation = new PathSolutionScript[popsize]; ///Pour chaque enfant que l'on doit générer par croisement for (int i = 0; i < popsize; i++) { ///Récupération de deux reproduteurs au hasard var parent1 = bestIndividuals[UnityEngine.Random.Range(0, bestIndividuals.Length)]; var parent2 = bestIndividuals[UnityEngine.Random.Range(0, bestIndividuals.Length)]; ///Création d'un individu à partir du croisement des deux parents newpopulation[i] = Crossover(parent1, parent2, nbPath); } ///Pour chaque individu de la population for (int i = 0; i < popsize; i++) { ///Tirage d'un nombre au hasard entre 0f et 1f float rdm = UnityEngine.Random.Range(0f, 1f); ///Comparaison de ce nombre au taux de mutation ///S'il est inférieur, on procède à la mutation if (rdm < mutationRate) { ///Mutation proposée : // On procède à une petite modification de la solution // courante. for(int j=0;j<mutationSequence;j++){ RandomChangeInSolution(newpopulation[i+j]); } } } ///Remplacement de l'ancienne population par la nouvelle population = newpopulation; iteration++; Debug.Log("Le nombre d'itérations est de : " + iteration); // On rend la main au moteur Unity3D yield return 0; } // Fin de l'algorithme, on indique que son exécution est stoppée _isRunning = false; TimeSpan simulation = DateTime.Now - time; string localCsvLine = _csvLog; if(DateTime.Compare(DateTime.Now,timeEnd)<0){ // On affiche le nombre d'itérations nécessaire à l'algorithme pour trouver la solution Debug.Log("CONGRATULATIONS !!! Solution Found in " + iteration + " iterations !"); localCsvLine += "00:00:"+simulation.TotalSeconds+";"+iteration+";Optimal;\n"; Debug.Log("En "+ simulation.TotalSeconds + " secondes !"); }else{ Debug.Log("FAIL !!! Solution not Found in " + iteration + " iteration and "+ simulation.TotalSeconds+" Seconds, or "+ simulation.TotalMinutes); localCsvLine += "00:00:"+simulation.TotalSeconds+";"+iteration+";Echec;\n"; } File.AppendAllText(@"log.csv",localCsvLine ); }