Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 35 additions & 16 deletions Evolution/Evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@
class Evolution:
# parameters
enum_max = 10
max_TTL = 10 # seconds
max_TTL = 30 # seconds
grace_period = 2 # seconds

random_const_amount = 100
random_const_min = -25
random_const_max = 25

generation = 5
generation = 100
max_depth = 8
population_size = 10
population_size = 100

mutation_rate_basic = 0.25
mutation_rate_critic = 0.25
Expand Down Expand Up @@ -154,13 +155,23 @@ def start_generation(self):
#returns true if simulation should be continued
def step(self,dt:float):
for ind in self.population:
if not ind.live:
continue
try:
float(ind.brain)
ind.check_speed()
if self.generation_timer > Evolution.grace_period:
ind.update_fitness(dt)
ind.check_height()
except:
ind.live = False
self.calc_fitness()
# self.calc_fitness()
self.generation_timer += dt

# Early exit if everyone is dead
if not any(ind.live for ind in self.population):
self.generation_timer = Evolution.max_TTL + 1 # Force end

# print(self.generation_timer)

def next_generation(self):
Expand All @@ -173,7 +184,7 @@ def next_generation(self):

def calc_fitness(self):
for ind in self.population:
if ind.live: ind.fitness = ind.getDistance()
if ind.live: ind.fitness = ind.calculate_fitness()

def find_best(self):
maybe_best = max(self.population, key=lambda x: (x.fitness,-1*x.brain.size,-1*x.brain.depth))
Expand All @@ -188,6 +199,7 @@ def find_best(self):
(maybe_best.fitness == self.best_fitness and maybe_best.brain.size == self.best_size and maybe_best.brain.depth < self.best_depth)):

self.best_brain = str(maybe_best.brain)
self.best_body = str(maybe_best.body_parameters)
self.best_fitness = maybe_best.fitness
self.best_size = maybe_best.brain.size
self.best_depth = maybe_best.brain.depth
Expand All @@ -208,6 +220,12 @@ def find_best(self):
f.write(f"{fitness_sum/Evolution.population_size}\n")
with open("average_size.txt","a") as f:
f.write(f"{size_sum/Evolution.population_size}\n")

try:
import graphs
graphs.update_plot()
except Exception as e:
print(f"Failed to update plot: {e}")

def check_for_stagnation(self):
if self.general_stagnation_count >= self.general_stagnation_constraint:
Expand All @@ -220,10 +238,10 @@ def check_for_stagnation(self):
def check_for_errors(self):
for i in range(len(self.population)-1,-1,-1):
if self.population[i].max_speed > self.anomaly_constraint:
self.population[i].live = False
if not self.population[i].live:# or self.population[i].fitness < self.left_anomaly_constraint:
ind = self.population.pop(i)
ind.die()
# self.population[i].live = False
# if not self.population[i].live:# or self.population[i].fitness < self.left_anomaly_constraint:
# ind = self.population.pop(i)
self.population[i].die()
# print("DIE")

#full
Expand Down Expand Up @@ -591,8 +609,8 @@ def tournament(self, rate: float) -> List[Individual]:

if max_fitness == min_fitness:
weights = [1.0] * len(fitness_values)
else:
weights = [(f - min_fitness) / (max_fitness - min_fitness) for f in fitness_values]
else: # todo sprawdzic ponizsza linie
weights = [((f - min_fitness) / (max_fitness - min_fitness)) + 1e-6 for f in fitness_values]

total_weight = sum(weights)
if total_weight == 0:
Expand Down Expand Up @@ -694,16 +712,17 @@ def check_if_are_duplicates(self):

def save(self):
self.population.sort(key=lambda x: (x.fitness),reverse=True)
best = self.population[0]
with open(Evolution.population_file, "w") as f:
for p in self.population:
f.write(str(p.brain)+"\n")
f.write(str(p.body_parameters) + "\n")
with open(Evolution.best_ind_file, "w") as f:
f.write(self.best_brain+"\n")
f.write(str(p.body_parameters) + "\n")
f.write(str(self.best_fitness)+"\n")
f.write(str(self.best_size)+"\n")
f.write(str(self.best_depth)+"\n")
f.write(str(best.brain) + "\n")
f.write(str(best.body_parameters) + "\n")
f.write(str(best.fitness) + "\n")
f.write(str(best.brain.size) + "\n")
f.write(str(best.brain.depth) + "\n")

def load_best_indvidual(self,filename:str,put_to_population:bool=False) :
with open(filename, "r") as f:
Expand Down
39 changes: 38 additions & 1 deletion Individual/Individual.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def __init__(self, space, ground_y):
self.body_parameters = None#[30, 60, 40, 50, 5, 100, 5, 40, 5, 10, 50, 5, 100, 5, 40, 5, 10]
self.live = True
self.fitness = 1
self.stability_time = 0.0

# Space and position initialization
self.space = space
Expand All @@ -132,6 +133,7 @@ def __init__(self, space, ground_y):
self.motor_bf_right = None

self.max_speed = 0
self.min_height_threshold = 15.0 # Distance from ground to chassis bottom

def create(self):
self.__create(self.space, self.ground_y)
Expand All @@ -143,6 +145,7 @@ def __create(self, space, ground_y):
self.start_positions = []
chassis_start_xy = Vec2d(100, self.ground_y - 150)
self.start_positions.append(chassis_start_xy)
Individual.used_ids.add(self.unique_id)

# Dimensions and properties
chassis_width, chassis_height = self.body_parameters[0:2]
Expand Down Expand Up @@ -253,6 +256,9 @@ def __create(self, space, ground_y):
self.max_speed = 0

def destroy_body(self):
if not self.live:
return

try:
assert all(isinstance(item, tuple) and len(item) == 12 for item in self.legs), "Leg components missing in self.legs"

Expand Down Expand Up @@ -319,15 +325,26 @@ def reset_individual_position(self):
self.motor_bf_right.rate = 0

def die(self):
if not self.live:
return
self.destroy_body()

self.live = False
Individual.used_ids.remove(self.unique_id)
Individual.used_ids.discard(self.unique_id)

def check_speed(self):
self.max_speed = max(np.sqrt(pow(self.chassis_body.velocity.x,2) + pow(self.chassis_body.velocity.y,2)),self.max_speed)
# print(self.max_speed)

def check_height(self):
chassis_height = self.body_parameters[1]
chassis_bottom_y = self.chassis_body.position.y + chassis_height / 2

height_above_ground = self.ground_y - chassis_bottom_y

if height_above_ground < self.min_height_threshold:
self.die()

def __str__(self):
return f"Individual: {self.unique_id}\nBrain: {self.brain}\nBody Parameters: {self.body_parameters}\nFitness: {self.fitness}\nLive: {self.live}"

Expand All @@ -348,6 +365,26 @@ def __to_one_number(self, x:Node, y:Node) -> float:
def getDistance(self) -> float:
return float(self.chassis_body.position.x - 100)

def update_fitness(self, dt: float):

chassis_height = self.body_parameters[1]
chassis_bottom_y = self.chassis_body.position.y + chassis_height / 2
height_above_ground = self.ground_y - chassis_bottom_y

is_above_threshold = height_above_ground >= self.min_height_threshold
# todo do sprawdzenia ponizsze
is_stable = abs(self.chassis_body.angle) < 0.7

distance = self.getDistance()

if distance < 0:
self.fitness = 0
else:
if is_above_threshold and is_stable:
self.stability_time += dt

self.fitness = distance + (self.stability_time * 10.0)

def getHeight(self,x:Node,y:Node) -> float:
return self.chassis_body.position.y

Expand Down
10 changes: 5 additions & 5 deletions best_ind_foot.txt

Large diffs are not rendered by default.

88 changes: 65 additions & 23 deletions graphs.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,73 @@
import os
import matplotlib.pyplot as plt

# Ścieżki do plików
base_path = "results/problem4"
# File paths (assuming files are in the current directory)
files = {
"average_fitness": os.path.join(base_path, "average_fitness.txt"),
"best_fitness": os.path.join(base_path, "best_fitness.txt"),
"average_size": os.path.join(base_path, "average_size.txt"),
"average_fitness": "average_fitness.txt",
"best_fitness": "best_fitness.txt",
"average_size": "average_size.txt",
}

def load_data(file_path):
if not os.path.exists(file_path):
# print(f"Warning: File {file_path} not found.")
return []
with open(file_path, "r") as file:
return [float(line.strip()) for line in file.readlines()]

# Wczytywanie danych z plików
data = {name: load_data(path) for name, path in files.items()}

for name, values in data.items():
plt.figure()
plt.plot(values, label=name.replace("_", " ").capitalize())
plt.title(f"{name.replace('_', ' ').capitalize()} Over Generations")
plt.xlabel("Generation")
plt.ylabel(name.replace("_", " ").capitalize())
plt.legend()
plt.grid()
output_path = os.path.join(base_path, f"{name}.png")
plt.savefig(output_path)
plt.close()

base_path # Return the path to verify correct output directory
return [float(line.strip()) for line in file.readlines() if line.strip()]

def update_plot():
# Load data
data = {name: load_data(path) for name, path in files.items()}

# Check if we have data
if not any(data.values()):
# print("No data found to plot.")
return

# Create figure and axis
fig, ax1 = plt.subplots(figsize=(10, 6))

# Plot Fitness on left Y-axis
color = 'tab:green'
ax1.set_xlabel('Generation')
ax1.set_ylabel('Fitness', color=color)

if data["best_fitness"]:
ax1.plot(data["best_fitness"], label='Best Fitness', color='green', linewidth=2)
if data["average_fitness"]:
ax1.plot(data["average_fitness"], label='Average Fitness', color='blue', linewidth=1.5, alpha=0.7)

ax1.tick_params(axis='y', labelcolor=color)
ax1.grid(True, which='both', linestyle='--', alpha=0.5)

# Create second Y-axis for Size
ax2 = ax1.twinx()
color = 'tab:red'
ax2.set_ylabel('Average Size', color=color)

if data["average_size"]:
ax2.plot(data["average_size"], label='Average Size', color=color, linestyle='--', linewidth=1.5)

ax2.tick_params(axis='y', labelcolor=color)

# Title and Layout
plt.title('Evolution Progress: Fitness & Size')
fig.tight_layout()

# Combine legends
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')

# Save plot
output_path = "simulation_results.png"
try:
plt.savefig(output_path, dpi=100)
# print(f"Plot saved to {output_path}")
except Exception as e:
print(f"Error saving plot: {e}")
finally:
plt.close(fig)

if __name__ == "__main__":
update_plot()
55 changes: 51 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,56 @@ def create_boundaries_2(self, width, height):
pin_shape.elasticity = 0.5 # Ustaw elastyczność dla kolizji
self.space.add(pin_body, pin_shape)

def draw(self):
def draw(self, evolution=None):
self.screen.fill(THECOLORS["white"]) ### Clear the screen
self.space.debug_draw(self.draw_options) ### Draw space

if evolution:
alive_count = sum(1 for ind in evolution.population if ind.live)
total_count = len(evolution.population)
best_fitness = evolution.best_fitness
generation = evolution.generation

stats_text = [
f"Generation: {generation}",
f"Alive: {alive_count}/{total_count}",
f"Best Fitness (Global): {best_fitness:.2f}",
f"Timer: {evolution.generation_timer:.1f}s"
]

font = pygame.font.SysFont('Arial', 16)
y_offset = 10
for line in stats_text:
text_surface = font.render(line, True, (0, 0, 0))
self.screen.blit(text_surface, (10, y_offset))
y_offset += 20

# Draw stability indicators
for ind in evolution.population:
if ind.live:
pos = ind.chassis_body.position

indicator_pos = (int(pos.x), int(pos.y - 30))

distance = ind.getDistance()
is_stable = abs(ind.chassis_body.angle) < 0.5

if distance < 0:
color = (0, 0, 255) # Blue for negative distance
elif is_stable:
color = (0, 255, 0) # Green for stable
else:
color = (255, 0, 0) # Red for unstable

pygame.draw.circle(self.screen, color, indicator_pos, 5)

threshold_y = self.ground_y - 15
pygame.draw.line(self.screen, (255, 0, 0), (0, threshold_y), (self.screen.get_width(), threshold_y), 2)

if evolution.generation_timer < Evolution.grace_period:
grace_text = font.render("GRACE PERIOD", True, (0, 200, 0))
self.screen.blit(grace_text, (10, y_offset))

pygame.display.update() ### All done, lets flip the display

def main(self,evolution:Evolution):
Expand All @@ -145,7 +192,7 @@ def main(self,evolution:Evolution):
simulate = True
evolution.start_generation()
while running:
self.draw()
self.draw(evolution)
iterations = 10
dt = 1.0 / float(self.fps) / float(iterations)
for event in pygame.event.get():
Expand Down Expand Up @@ -173,8 +220,8 @@ def main(self,evolution:Evolution):
if __name__ == '__main__':
sim = Simulator()
evo = Evolution(sim.space,sim.ground_y,sim.fps)
# evo.clear_population()
# evo.load_population("population_foot.txt")
evo.clear_population()
evo.load_population("population_foot.txt")
# evo.load_best_indvidual("best_ind_foot.txt",put_to_population=True)
# evo.load_population("results/problem2/population_problem2.txt")
# evo.load_best_indvidual("results/problem2/best_ind_problem_2.txt",put_to_population=True)
Expand Down
Loading