Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/CogitoNTNU/CrawlAI
Browse files Browse the repository at this point in the history
  • Loading branch information
tobiasfremming committed Nov 11, 2024
2 parents f0f1bb9 + 1201351 commit 7b9fef6
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 98 deletions.
78 changes: 31 additions & 47 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,7 @@

from src.agent_parts.rectangle import Point
from src.genetic_algoritm import GeneticAlgorithm
from src.globals import (
FONT_SIZE,
SEGMENT_WIDTH,
BLACK,
RED
)
from src.globals import FONT_SIZE, SEGMENT_WIDTH, BLACK, RED
from src.agent_parts.creature import Creature
from src.NEATnetwork import NEATNetwork

Expand All @@ -37,31 +32,20 @@ def create_creatures(amount, space):
vision = Vision(Point(0, 0))
creature: Creature = Creature(space, vision)
# Add limbs to the creature, placing them above the ground
# limb1 = creature.add_limb(100, 60, (300, 100), mass=1)
# limb2 = creature.add_limb(100, 20, (350, 100), mass=3)
# limb1 = creature.add_limb(100, 60, (300, 100), mass=1)
# limb2 = creature.add_limb(100, 20, (350, 100), mass=3)
# limb3 = creature.add_limb(110, 60, (400, 100), mass=5)

limb1 = creature.add_limb(100, 20, (300, 300), mass=1)
limb2 = creature.add_limb(100, 20, (350, 300), mass=3)
limb3 = creature.add_limb(80, 40, (400, 300), mass=5)

# Add a motor between limbs
creature.add_motor(
limb1,
limb2,
(50, 0),
(-25, 0),
rate=0, tolerance=30)
creature.add_motor(
limb2,
limb3,
(37, 0),
(-23, 0),
rate=0,
tolerance=50)

creature.add_motor(limb1, limb2, (50, 0), (-25, 0), rate=0, tolerance=30)
creature.add_motor(limb2, limb3, (37, 0), (-23, 0), rate=0, tolerance=50)

creatures.append(creature)

return creatures


Expand All @@ -74,10 +58,12 @@ def create_population(population_size, creature: Creature):
amount_of_in_nodes += amount_of_joints
# limb positions
amount_of_in_nodes += creature.get_amount_of_limb() * 2

population = GeneticAlgorithm.initialize_population(population_size, amount_of_in_nodes, amount_of_out_nodes)

return population
population = GeneticAlgorithm.initialize_population(
population_size, amount_of_in_nodes, amount_of_out_nodes
)

return population


def apply_mutations(self):
Expand All @@ -89,7 +75,6 @@ def apply_mutations(self):
MUTATION_RATE_CONNECTION = 0.05 # 5% chance to add a new connection
MUTATION_RATE_NODE = 0.03 # 3% chance to add a new node


# Mutate weights with a certain probability
if random.random() < MUTATION_RATE_WEIGHT:
self.mutate_weights(delta=0.1) # Adjust delta as needed
Expand All @@ -111,11 +96,11 @@ def main():
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Pymunk Rectangle Physics")
interface = Interface()

# Track whether physics is on or off
physics_on = False
physics_value = 0

# Set up the Pymunk space
space = pymunk.Space()
space.gravity = (0, 981) # Gravity pointing downward
Expand All @@ -124,15 +109,15 @@ def main():
environment.activate_death_ray()
environment.ground_type = GroundType.BASIC_GROUND

#Population and creatures
# Population and creatures
population_size = 5
creatures: list[Creature] = create_creatures(population_size, space)
creature_instance: Creature = creatures[0]
population = create_population(population_size, creature_instance)
neat_networks: list[NEATNetwork] = []
for genome in population:
neat_networks.append(NEATNetwork(genome))

clock = pygame.time.Clock()
vision_y = 100
vision_x = 0
Expand All @@ -143,20 +128,24 @@ def main():
running = False
interface.handle_events(event)

space.step(1/60.0)
space.step(1 / 60.0)
screen.fill((135, 206, 235))
environment.update()
environment.render()

for index, creature in enumerate(creatures):
inputs = np.array([creature.vision.get_near_periphery().x,
creature.vision.get_near_periphery().y,
creature.vision.get_far_periphery().x,
creature.vision.get_far_periphery().y])
inputs = np.array(
[
creature.vision.get_near_periphery().x,
creature.vision.get_near_periphery().y,
creature.vision.get_far_periphery().x,
creature.vision.get_far_periphery().y,
]
)
network = population[index]
for joint_rate in creature.get_joint_rates():
inputs = np.append(inputs, joint_rate)

for limb in creature.limbs:
inputs = np.append(inputs, limb.body.position.x)
inputs = np.append(inputs, limb.body.position.y)
Expand All @@ -167,27 +156,22 @@ def main():
vision_y = round(creature.limbs[0].body.position.y)
vision_x = round(creature.limbs[0].body.position.x)
creature.vision.update(
Point(vision_x, vision_y),
environment.ground,
environment.offset) # if perlin, offset = 0, if basic, offset = environment.offset
Point(vision_x, vision_y), environment.ground, environment.offset
) # if perlin, offset = 0, if basic, offset = environment.offset

if creature.limbs[0].body.position.y > SCREEN_HEIGHT:
creatures.remove(creature)
if environment.death_ray is not None:
if creature.limbs[0].body.position.x < environment.death_ray.get_x():
creatures.remove(creature)
creature.render(screen)

clock.tick(60)

pygame.display.flip()

pygame.quit()




if __name__ == "__main__":
main()


98 changes: 98 additions & 0 deletions neat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import random
from typing import List, Tuple

from src.genome import Genome, Innovation
from src.species import Species


class GeneticAlgorithm:
def __init__(self, population_size: int, num_inputs: int, num_outputs: int):
self.population_size = population_size
self.num_inputs = num_inputs
self.num_outputs = num_outputs
self.population: List[Genome] = []
self.species_list: List[Species] = []
self.innovation = Innovation.get_instance()
self.genome_id_counter = 0
self.species_id_counter = 0

self.initialize_population()

def initialize_population(self):
for _ in range(self.population_size):
genome = Genome(
genome_id=self.genome_id_counter,
num_inputs=self.num_inputs,
num_outputs=self.num_outputs,
)
self.population.append(genome)
self.genome_id_counter += 1

def evaluate_fitness(self, evaluate_function):
for genome in self.population:
genome.fitness = evaluate_function(genome)

def speciate(self, compatibility_threshold: float):
self.species_list = []
for genome in self.population:
found_species = False
for species in self.species_list:
representative = species.members[0]
distance = genome.compute_compatibility_distance(representative)
if distance < compatibility_threshold:
species.add_member(genome)
genome.species = species.species_id
found_species = True
break
if not found_species:
new_species = Species(self.species_id_counter)
new_species.add_member(genome)
genome.species = new_species.species_id
self.species_list.append(new_species)
self.species_id_counter += 1

def adjust_fitness(self):
for species in self.species_list:
species.adjust_fitness()

def reproduce(self):
new_population = []
total_average_fitness = sum(
species.average_fitness for species in self.species_list
)
for species in self.species_list:
offspring_count = int(
(species.average_fitness / total_average_fitness) * self.population_size
)
for _ in range(offspring_count):
parent1 = species.select_parent()
if random.random() < 0.25:
# Mutation without crossover
child = parent1.copy()
child.mutate()
else:
parent2 = species.select_parent()
child = parent1.crossover(parent2)
child.mutate()
child.genome_id = self.genome_id_counter
self.genome_id_counter += 1
new_population.append(child)
# If we don't have enough offspring due to rounding, fill up the population
while len(new_population) < self.population_size:
parent = random.choice(self.population)
child = parent.copy()
child.mutate()
child.genome_id = self.genome_id_counter
self.genome_id_counter += 1
new_population.append(child)
self.population = new_population

def evolve(
self, generations: int, evaluate_function, compatibility_threshold: float
):
for generation in range(generations):
print(f"Generation {generation+1}")
self.evaluate_fitness(evaluate_function)
self.speciate(compatibility_threshold)
self.adjust_fitness()
self.reproduce()
56 changes: 28 additions & 28 deletions src/genome.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import random
from dataclasses import dataclass
from typing import List
from typing import List


class Inovation:
Expand All @@ -27,8 +27,7 @@ def _get_innovation_number(in_node, out_node):
return Inovation._innovation_history[key]
else:
Inovation._global_innovation_counter += 1
Inovation._innovation_history[key] =\
Inovation._global_innovation_counter
Inovation._innovation_history[key] = Inovation._global_innovation_counter
return Inovation._innovation_history[key]


Expand All @@ -51,7 +50,7 @@ class Connection:

def change_enable(self, status: bool):
self.enabled = status


class Genome:
def __init__(self, genome_id: int, num_inputs: int, num_outputs: int):
Expand All @@ -64,24 +63,25 @@ def __init__(self, genome_id: int, num_inputs: int, num_outputs: int):

# Create input nodes
for i in range(num_inputs):
self.nodes.append(Node(id=i, node_type='input'))
self.nodes.append(Node(id=i, node_type="input"))

# Create output nodes
for i in range(num_outputs):
self.nodes.append(Node(id=num_inputs + i, node_type='output'))
self.nodes.append(Node(id=num_inputs + i, node_type="output"))

# Connect each input node to each output node with a random weight
for input_node in range(num_inputs):
for output_node in range(num_outputs):
self.connections.append(Connection(
in_node=input_node,
out_node=num_inputs + output_node,
weight=random.uniform(-1.0, 1.0),
innovation_number=Inovation.get_instance()._get_innovation_number(
input_node,
num_inputs + output_node
self.connections.append(
Connection(
in_node=input_node,
out_node=num_inputs + output_node,
weight=random.uniform(-1.0, 1.0),
innovation_number=Inovation.get_instance()._get_innovation_number(
input_node, num_inputs + output_node
),
)
))
)

def mutate_weights(self, delta: float):
for conn in self.connections:
Expand All @@ -100,9 +100,7 @@ def mutate_connections(self):
out_node.id,
weight,
enabled,
Inovation.get_instance()._get_innovation_number(
in_node.id,
out_node.id)
Inovation.get_instance()._get_innovation_number(in_node.id, out_node.id),
)
self.connections.append(new_conn)

Expand All @@ -112,17 +110,19 @@ def mutate_nodes(self):
con.change_enable(False)

con1 = Connection(
con.in_node,
new_node.id,
1.0,
True,
Inovation.get_instance()._get_innovation_number(con.in_node, new_node.id))
con.in_node,
new_node.id,
1.0,
True,
Inovation.get_instance()._get_innovation_number(con.in_node, new_node.id),
)
con2 = Connection(
new_node.id,
con.out_node,
con.weight,
True,
Inovation.get_instance()._get_innovation_number(new_node.id, con.out_node))
new_node.id,
con.out_node,
con.weight,
True,
Inovation.get_instance()._get_innovation_number(new_node.id, con.out_node),
)

self.connections.extend([con1, con2])
self.nodes.append(new_node)
Expand All @@ -137,4 +137,4 @@ def __eq__(self, other):
return self.id == other.id

def __lt__(self, other):
return self.fitness < other.fitness
return self.fitness < other.fitness
Loading

0 comments on commit 7b9fef6

Please sign in to comment.