On considère l'ensemble des échiquiers comportant sur chacune de leurs cases soit un pion blanc, soit rien. Cet ensemble est à peu de choses près l'ensemble {0,1}^64.
On exclut de les échiquiers comportant au plus une case vide
Pour un échiquier de E donné et deux cases vides x et y de E, on définit le coût de la paire (x,y) comme étant le nombre minimal de déplacements nécessaires à une dame blanche pour se déplacer de x à y. Si le déplacement est impossible, par exemple si l'échiquier n'est pas connexe, on considèrera que le coût est nul.
Le score d'un échiquier est le coût maximal calculé sur toutes les paires de cases libres
Par exemple, le score d'un échiquier vide est 2, car certaines paires de cases sont jointes en deux mouvements de dame, et d'autres sont jointes en un seul mouvement.
Quel est le maximum des scores des échiquiers de ? Combien de pions au minimum faut-il placer pour obtenir un tel score ? Donner un exemple d'échiquier
Je ne connais pas le max non plus, mais @Vassilia j'ai trouvé le même résultat que toi, hormis que tu as inclus deux pions supplémentaires qui ne sont pas nécessaires pour que l'échiquier ait un score de 23
J'avoue ne pas avoir trop fait attention aux nombres de pions,
je peux en effet supprimer le H5 et le G8
D'accord alors H5, G8 et A2 ce qui nous fait enlever 3 pions
En effet, même sans G8 pour la fin du parcours, si on veut atteindre H7, il faudra 2 déplacements à partir de F8 qui est obligatoire.
D'ailleurs finalement, enlevons aussi B1 car si on part de A2, on a 2 déplacements pour atteindre C1 qui est lui aussi obligatoire.
Donc ça devrait être jouable avec 30 pions. Pas de meilleure idée d'optimisation ce soir et il est l'heure d'aller dormir pour moi.
Bonne nuit.
Bonjour,
Bonjour
Tu télécharges ton image comme d'habitude
Puis dans la zone de réponse tu cliques sur l'icône de blank, ton curseur est alors entre les balises
Puis tu cliques sur ton image
Et le code de ton image est alors coincé entre les balises de blank
Haha le boulet ! Bien vu et merci
Du coup en tentant de réparer mon erreur je trouve qu'on peut faire encore mieux
Bonsoir,
Juste pour le délire, un code Python pour résoudre le problème. Pas mieux que 25.
Principe :
* On donne une case start et une case stop
* Un algo génétique génère des configurations. Initialement, j'ai choisi une proba de 0.5 pour choisir si une case est bloquée ou autorisée car c'est proche de que donnent les bonnes solutions trouvées par nous tous. De plus, avoir trop de cases libres ferait ramer l'algorithme de Dijkstra pour la recherche du meilleur chemin et on "voit bien" que moins on met de contraintes moins il faut de coups à la dame pour atteindre l'objectif.
** Du coup, le fitness correspond au résultat de Dijkstra lancé sur la config
** Le crossover correspond à récupérer N//2 cases choisies aléatoirement du parent 1 et le reste du parent 2.
** La mutation consiste à changer l'état d'une case
Évidement il y a plein de voies d'amélioration. Je pense que partir d'un échiquier 100% contraint et libérer de proche en proche des cases pourrait donner de bonnes solutions avec beaucoup moins de calculs (plus besoin de Dijkstra)... mais bon j'ai démarré comme ça... alors je laisse.
Si vous lancez le code ci-dessous, vous en avez pour la nuit (il teste toutes les combinaisons start, stop, chacun parcourant {0,1,2,3}^2 donc 256 algo G à lancer. Pour lancer l'algo qu'une fois, on peut commenter le dernier bloc et décommenter l'avant dernier, ce qui lance le calcul avec start=(0,0) et stop=(7,7)
import heapq
import random
def dijkstra_chessboard(start_pos, end_pos, obstacles):
size = 8
distances = [[float('inf')] * size for _ in range(size)]
distances[start_pos[0]][start_pos[1]] = 0
previous = [[None] * size for _ in range(size)]
pq = [(0, start_pos)]
while pq:
cost, curr_pos = heapq.heappop(pq)
if curr_pos == end_pos:
return construct_path(previous, start_pos, end_pos), cost
for direction in [(0, 1), (0, -1), (1, 0), (-1, 0), (1, 1), (-1, -1), (1, -1), (-1, 1)]:
for i in range(1, size):
new_pos = (curr_pos[0] + i * direction[0], curr_pos[1] + i * direction[1])
if 0 <= new_pos[0] < size and 0 <= new_pos[1] < size:
if new_pos in obstacles:
break
new_cost = cost + 1
if new_cost < distances[new_pos[0]][new_pos[1]]:
distances[new_pos[0]][new_pos[1]] = new_cost
previous[new_pos[0]][new_pos[1]] = curr_pos
heapq.heappush(pq, (new_cost, new_pos))
return [], 0
def construct_path(previous, start_pos, end_pos):
path = []
curr_pos = end_pos
while curr_pos != start_pos:
path.append(curr_pos)
curr_pos = previous[curr_pos[0]][curr_pos[1]]
path.append(start_pos)
return path[::-1]
def draw_chessboard(size, obstacles, trajectory):
for i in range(size):
for j in range(size):
if (i, j) in obstacles:
print("X", end=" ")
elif (i, j) in trajectory:
print("*", end=" ")
else:
print(".", end=" ")
print()
return
def generate_chessboard(size):
return [[False] * size for _ in range(size)]
def generate_individual(size, obstacle_probability, start_pos, end_pos):
individual = generate_chessboard(size)
for i in range(size):
for j in range(size):
if random.random() < obstacle_probability:
individual[i][j] = True
individual[start_pos[0]][start_pos[1]] = False
individual[end_pos[0]][end_pos[1]] = False
return individual
def get_obstacles(individual):
obstacles = []
for i in range(len(individual)):
for j in range(len(individual[i])):
if individual[i][j] == True:
obstacles.append((i, j))
return obstacles
def mutate(individual, mutation_probability, start_pos, end_pos):
size = len(individual)
mutated_individual = [row[:] for row in individual]
for i in range(size):
for j in range(size):
if random.random() < mutation_probability:
mutated_individual[i][j] = not mutated_individual[i][j]
mutated_individual[start_pos[0]][start_pos[1]] = False
mutated_individual[end_pos[0]][end_pos[1]] = False
return mutated_individual
def crossover(parent1, parent2, start_pos, end_pos):
size = len(parent1)
half_size = size // 2
child1 = [[None] * size for _ in range(size)]
child2 = [[None] * size for _ in range(size)]
selected_indices = random.sample(range(size * size), half_size)
selected_positions = [(i // size, i % size) for i in selected_indices]
for i in range(size):
for j in range(size):
if (i, j) in selected_positions:
child1[i][j] = parent1[i][j]
child2[i][j] = parent2[i][j]
else:
child1[i][j] = parent2[i][j]
child2[i][j] = parent1[i][j]
child1[start_pos[0]][start_pos[1]] = False
child1[end_pos[0]][end_pos[1]] = False
child2[start_pos[0]][start_pos[1]] = False
child2[end_pos[0]][end_pos[1]] = False
return child1, child2
# Retirer les obstacles inutiles (réduit le nombre de fitness=0)
def cleanup_chessboard(individual, trajectory, obstacles):
for o in obstacles:
if not set.intersection(
{(o[0]-1, o[1]+1), (o[0], o[1]+1), (o[0]+1, o[1]+1),
(o[0]-1, o[1]), (o[0], o[1]), (o[0]+1, o[1]),
(o[0]-1, o[1]-1), (o[0], o[1]-1), (o[0]+1, o[1]-1)},
set(trajectory)):
individual[o[0]][o[1]] = False
return
def fitness_function(individual, start_pos, end_pos):
obstacles = get_obstacles(individual)
trajectory = dijkstra_chessboard(start_pos, end_pos, obstacles)
if trajectory[1] > 20: # Sans cette condition, Dijkstra prend trop de temps
cleanup_chessboard(individual, trajectory[0], obstacles)
return trajectory[1]
def genetic_algorithm(size, start_pos, end_pos, population_size, obstacle_probability, mutation_probability, num_generations, hint=[]):
population = hint + [generate_individual(size, obstacle_probability, start_pos, end_pos) for _ in range(population_size - len(hint))]
for generation in range(num_generations):
#print(f"Generation {generation+1}")
fitness_scores = [fitness_function(individual, start_pos, end_pos) for individual in population]
best_individual = population[fitness_scores.index(max(fitness_scores))]
best_fitness = max(fitness_scores)
#print(f"Best Fitness: {best_fitness}")
new_population = [best_individual]
while len(new_population) < population_size:
parent1 = random.choices(population, weights=fitness_scores)[0]
parent2 = random.choices(population, weights=fitness_scores)[0]
child1, child2 = crossover(parent1, parent2, start_pos, end_pos)
mutated_child1 = mutate(child1, mutation_probability, start_pos, end_pos)
mutated_child2 = mutate(child2, mutation_probability, start_pos, end_pos)
new_population.append(mutated_child1)
new_population.append(mutated_child2)
population = new_population
best_individual = population[fitness_scores.index(max(fitness_scores))]
best_fitness = max(fitness_scores)
#print(f"\nBest Individual: {best_individual}")
print(f"Best Fitness: {best_fitness}")
return best_individual
size = 8
start_pos = (0, 0)
end_pos = (7, 7)
population_size = 200
obstacle_probability = 0.5
mutation_probability = 0.01
num_generations = 500
best=[]
hint=[]
# Utilisation basique (start et stop connus). Décommenter
##best = genetic_algorithm(size, start_pos, end_pos, population_size, obstacle_probability, mutation_probability, num_generations, [])
##s,e,o = start_pos, end_pos, get_obstacles(best)
##best_trajectory = dijkstra_chessboard(s,e,o)[0]
##draw_chessboard(size, o, best_trajectory)
# Le code ci-dessous demande beaucoup plus de temps
# Utilisation avec boucles sur start parcourant le quart Nord-Ouest et stop parcourant le quart Sud Est
# Utiliser le best précédent comme élément de la population initiale
for start_pos in ((x,y) for x in range(4) for y in range(4)):
for end_pos in ((7-x,7-y) for x in range(4) for y in range(4)):
hint_temp=[]
for h in hint:
temp = list(h)
temp[start_pos[0]][start_pos[1]] = False
temp[end_pos[0]][end_pos[1]] = False
hint_temp.append(h)
current_best = genetic_algorithm(size, start_pos, end_pos, population_size, obstacle_probability, mutation_probability, num_generations, hint_temp)
best.append(current_best)
s,e,o = start_pos, end_pos, get_obstacles(current_best)
best_trajectory = dijkstra_chessboard(s,e,o)[0]
draw_chessboard(size, o, best_trajectory)
hint.append(current_best)
Best Fitness: 25
* X X * * X * *
* . * X . * X .
X X X X X X X *
X * * X X * * X
* X X * * X X X
X * X X X X X *
* X X * X * X *
X * * X * X * .
Best Fitness: 25
* X * * X X * *
X * . X * * X .
X X X X X X X *
* . * X X * * X
* X X * * X X X
X * X X X X * *
* X X * * X X .
* . * X X * . *
Best Fitness: 25
* X X * * X * *
* . * X X * X .
X X X X X X X *
X * * X X X * X
* X X * * X * X
X * X X X X X *
* X X * * X X *
X * * X X * * X
Best Fitness: 25
X * * X X * * X
* X X X * X X *
X * X X * X X *
* X X * X X * X
X * X X * X * X
* . X * X X X *
* X X * X * X *
X * * X X X * .
Best Fitness: 25
X X * X X * * X
* * X * * X X *
X X X X X X X *
* . * X X * * X
* X X * * X X X
X * X X X X . *
* X X * * X X *
X * * X X * * X
Best Fitness: 25
X X * X X * * X
* * X * * X X *
X X X X X X X *
* . * X X * * X
* X X * * X X X
X * X X X X * .
* X X * * X X *
X * * X X * * X
Je n'ai pas écrit beaucoup d'algorithmes (le côté aléatoire sûrement ^^) mais j'ai une idée pour accélérer le calcul de la fitness (Algorithme de Floyd-Marshal à la place de Dijkstra).
Avec ce nouvel algorithme, on calcule directement le diamètre du graphe au lieu de ne regarder qu'un point de départ et d'arrivée.
Mais je me demandais si le fait de définir un point d'arrivée et de départ n'améliorait pas le crossover. En effet, sans départ et arrivée fixe il y a plus de chances que les parents n'aient rien en commun. Autrement dit, moins de chance de générer des enfants aussi bons que leurs parents. thetapinch27, tu en penses quoi?
Est-ce qu'ajouter une méthode de matching entre parents pallierait à ce problème? Est-ce que ça se fait en algorithme génétique?
Il faut lire : "Je n'ai pas écrit beaucoup d'algorithmes génétique"
J'ai écrit beaucoup d'algorithmes mais rarement génétiques
Bonjour,
@LittleFox
Je ne suis pas très étonné, les algo G sont quelquefois "méprisés" (surtout par les mathématiciens ... je fais me faire assassiner ) car ils s'apparentent plus à de la cuisine qu'à de la science (on n'a aucune garantie sur rien). Mais ils ont un intérêt immense dans beaucoup de problèmes concrets car souvent ce qui importe ce n'est pas d'avoir la meilleure solution mais un ensemble de solutions pas trop mauvaises et acceptables.
Pour Dijkstra, je pense que le coût calculatoire est faible dans ce cas particulier car j'utilise l'algo avec en moyenne 1 case sur 2 bloquée. Donc le "branching factor" est toujours faible. Mais en effet, si je réduis la proba de blocage d'une case (30% par exemple), le temps de calcul explose mais les solutions trouvées ne sont pas aussi bonnes qu'avec 0.5. C'est effectivement une restriction qui limite la capacité d'exploration.
Pour le second point : en fait l'algo tourne déjà à (start, stop) imposé donc tous les parents et tous les enfants ont toujours le même (start, stop). J'ai ensuite une boucle qui lance l'algo G autant de fois qu'il y a de couples (start, stop) - voir le dernier bloc. D'ailleurs pour des raisons de symétrie j'aurais pu me limiter à parcourir le quart NO pour start et la moitié du quart SE pour l'arrivée. Ce qui ferait 160 run au lieu de 256.
La perf globale n'est pas terrible malgré tout. Certains "best" sont améliorables avec un tout petit peu de bon sens. Parfois, lancer l'algo G plusieurs fois avec le même (start, stop) conduit à des fitness différents même s'ils sont toujours pas trop mauvais. Faudrait comparer avec ce que donnerait une méthode brute-force pour dire si le qualificatif "génétique" est utile ici.
Voici ma tentative pour un algorithme génétique
Forte utilisation de numpy
Floyd-Warshall, on est agnostique du départ et de l'arrivée. Et indépendant du nombre de pions sur l'échiquier.
Le plus dur aura été de calculer la matrice d'adjacence tout en utilisant la vectorisation de numpy.
J'ai tenu compte du nombre de pions dans la fitness avec une approche où on en tient peu compte pour les 100 premières générations puis de plus en plus.
J'ai eu des soucis où l'ensemble de la population était composée de clones du même individu. Maintenant, je supprime tous les clones avant de calculer la fitness. Ça à l'air de marcher
La fitness est normalisée de façon à ce que le meilleur ait deux fois plus de chance que le plus mauvais.
Je garde les 10 meilleurs individus pour empêcher ma population de régresser.
On pourrait augmenter la probabilité de mutation quand la population stagne. Je ne l'ai pas fait.
Le code est disponible sur github:
* * X * * X * *
. X * . X * X .
* X X X X X X *
X * X * . X * X
* X X X * X * X
* . X * X X X *
X * X * X * X *
* . X . * X * .
Generation: 1831
* * X * * X * *
. . X . X * X .
X * X * X X X *
* X X X * X * X
* . X * . X X *
X * X X X X * X
* X X * * X X *
* . * X X * * .
(25, 30)
Vous devez être membre accéder à ce service...
Pas encore inscrit ?
1 compte par personne, multi-compte interdit !
Ou identifiez-vous :