-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathevolution_numpy.py
More file actions
113 lines (91 loc) · 4.42 KB
/
Copy pathevolution_numpy.py
File metadata and controls
113 lines (91 loc) · 4.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
"""Evolutionary generation update logic for the raceline optimizer.
This module is responsible for evaluating the current population of controllers,
selecting the most promising individuals, and producing the next generation of
cars with mutated or blended neural policies.
"""
import copy
import numpy as np
from car_numpy import Car
from nn_numpy import NeuralCar
import config
def next_generation(state):
"""Advance the simulation by creating the next generation of agents.
The routine ranks the current population by fitness, tracks global records,
applies adaptive mutation based on stagnation, and constructs a new set of
cars whose policies are derived from elite survivors and top performers.
"""
cars = state['cars']
settings = state['settings']
scored_cars = sorted(cars, key=lambda c: c.score, reverse=True)
winner = scored_cars[0]
scores = [c.score for c in cars]
avg_score = np.mean(scores)
std_score = np.std(scores)
max_score = max(scores)
finished_count = sum(1 for c in cars if c.finished)
print(f"Gen {state['generation']} | Best: {max_score:.1f} | Avg: {avg_score:.1f}±{std_score:.1f} | Finished: {finished_count}/{len(cars)}")
prev_best = state.get('all_time_record_score', -float('inf'))
improved = False
if winner.score > prev_best:
state['all_time_record_score'] = winner.score
state['all_time_best_brain'] = winner.brain.get_weights()
improved = True
if winner.finished:
state['best_raceline'] = winner.get_path()
state['all_time_best_lap_time'] = winner.lap_time
print(f" NEW RECORD! Score: {winner.score:.1f} | Time: {winner.lap_time:.2f}s") # FIXED: no /60
else:
print(f" Progress record: {winner.score:.1f} (didn't finish)")
stagnation = state.get('stagnation_counter', 0)
if improved:
stagnation = 0
else:
stagnation += 1
state['stagnation_counter'] = stagnation
base_mutation = settings['mutation_rate']
if stagnation > 5:
adaptive_mutation = min(0.8, base_mutation * (1 + stagnation * 0.1))
print(f" Stagnation: {stagnation} gens, mutation increased to {adaptive_mutation:.2f}")
elif stagnation == 0:
adaptive_mutation = base_mutation * 0.5
else:
adaptive_mutation = base_mutation
scores_arr = np.array([c.score for c in cars])
sorted_indices = np.argsort(scores_arr)[::-1]
top_count = max(2, int(settings['num_cars'] * 0.3))
top_indices = sorted_indices[:top_count]
new_cars = []
best_brain_weights = state.get('all_time_best_brain')
for i in range(settings['num_cars']):
if i == 0 and best_brain_weights is not None:
# Elite clone (unchanged)
parent_brain = NeuralCar(weights=best_brain_weights)
child_brain = parent_brain.clone_brain()
elif i == 1 and best_brain_weights is not None:
# Elite + light mutation
parent_brain = NeuralCar(weights=best_brain_weights)
child_brain = parent_brain.clone_brain()
child_brain.mutate(adaptive_mutation * 0.2)
elif i < 4 and best_brain_weights is not None:
# Elite + medium mutation
parent_brain = NeuralCar(weights=best_brain_weights)
child_brain = parent_brain.clone_brain()
child_brain.mutate(adaptive_mutation * 0.5)
elif i < 6 and len(top_indices) >= 2:
# NEW: Crossover between two top performers
p1_idx = top_indices[i % len(top_indices)]
p2_idx = top_indices[(i + 1) % len(top_indices)]
child_brain = NeuralCar.blend(cars[p1_idx].brain, cars[p2_idx].brain, alpha=0.5)
child_brain.mutate() # Uses self-adaptive rate from parents
else:
# Standard: mutate a top parent (self-adaptive)
parent_idx = top_indices[i % len(top_indices)]
parent = cars[parent_idx]
child_brain = parent.brain.clone_brain()
child_brain.mutate() # No rate passed -> uses inherited self-adaptive rate
car = Car(state['track'], child_brain, i, settings['speed_mult'], rng_seed=i + state['generation'])
car.brain.reset_memory() # ← ADD THIS
new_cars.append(car)
state['cars'] = new_cars
state['generation'] += 1
state['frame_counter'] = 0