Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ab7ecaf
Data for experiments
MaorMalka Jan 15, 2025
4747a37
Update student.py
MaorMalka Jan 15, 2025
8cd8394
Update helperFunctions.py
MaorMalka Jan 15, 2025
f41fec4
Updated algorithms
MaorMalka Jan 15, 2025
118b6e3
Added colored print
MaorMalka Jan 15, 2025
430f77f
Added iterations experiments
MaorMalka Jan 15, 2025
4202b76
Merge pull request #17 from Groupify-SCE/Feature/new-experiments
NadavMozeson Jan 28, 2025
f830154
Finished new DFS initialize groups algorithm
NadavMozeson Feb 20, 2025
57b8684
Merge pull request #18 from Groupify-SCE/Feature/new-initial-division
MaorMalka Feb 24, 2025
7652dc0
Added data files for experiments
NadavMozeson Feb 24, 2025
645e695
Finished writing experiments
NadavMozeson Feb 24, 2025
cbfb06f
Removed data of test experiments
NadavMozeson Feb 24, 2025
891c445
Removed sample experiments results
NadavMozeson Feb 24, 2025
8ef8519
Create experimentsFunctions.py
NadavMozeson Feb 24, 2025
4b6076c
Fixed infinite loop
NadavMozeson Feb 24, 2025
ec67db5
Update experimentsFunctions.py
NadavMozeson Feb 24, 2025
6102005
Update InitialDivision.py
NadavMozeson Feb 24, 2025
cf4424e
Update experimentsFunctions.py
NadavMozeson Mar 1, 2025
6ce09ef
Update experimentsFunctions.py
NadavMozeson Mar 2, 2025
56ba716
Update experimentsFunctions.py
NadavMozeson Mar 4, 2025
2f8e11e
Update experimentsFunctions.py
NadavMozeson Mar 10, 2025
a77f08e
Merge pull request #19 from Groupify-SCE/Feature/perform-experiments
NadavMozeson Mar 25, 2025
e977951
Merge branch 'stage' into dev
NadavMozeson Mar 25, 2025
e7d5053
Merge pull request #20 from Groupify-SCE/dev
NadavMozeson Mar 25, 2025
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: 9 additions & 42 deletions ABC/PrefrencesABC.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,10 @@
import statistics
from typing import List
from utils.student import Student
from components.InitialDivision import initialize_groups

GLOBAL_MAX = []
GLOBAL_MAX_VAL = float("-inf")
def initialize_groups(students: List[Student], num_groups: int) -> List[List[Student]]:
"""
הערה מקורית: יוצר קבוצות התחלתיות
מה הוספנו? לא ביצענו באופן אקראי לחלוטין, אנחנו מתאימים תלמידים לפי התאמות אישיות
"""
# ניצור את הקבוצות ריקות
groups = [[] for _ in range(num_groups)]
# נשמור תלמידים שהכנסנו לקבוצה
assigned_students = set()

# נחשב את הגודל המקסימלי של כל קבוצה
max_group_size = len(students) // num_groups + (1 if len(students) % num_groups != 0 else 0)

random.shuffle(students)
# נרוץ על כל תלמיד, ננסה להכניס אותו לקבוצה רק אם יש לו את אחת מהעדפות שלו בקבוצה
# ואם הקבוצה לא עברה את הגודל המקסימלי
for student in students:
for group in groups:
if len(group) < max_group_size and any(preference in [s.id for s in group] for preference in student.preferences):
group.append(student)
assigned_students.add(student.id)
break

# אם התלמיד לא הוכנס לאף קבוצה, נכניס אותו לקבוצה הכי קטנה (אם היא לא מלאה)
if student.id not in assigned_students:
smallest_group = min(groups, key=len)
if len(smallest_group) < max_group_size:
smallest_group.append(student)
assigned_students.add(student.id)

# נרוץ על התלמידים שעוד לא הוכנסו לקבוצות, כל אחד מהם בתורו נכניס לקבוצה הקטנה ביותר (אם היא לא מלאה)
remaining_students = [student for student in students if student.id not in assigned_students]
for student in remaining_students:
for group in groups:
if len(group) < max_group_size:
group.append(student)
break

return groups

def calculate_diversity(groups: List[List[Student]]) -> float:
"""
הערה מקורית: פונקציית חישוב הגיוון של תוצאה
Expand All @@ -54,7 +15,14 @@ def calculate_diversity(groups: List[List[Student]]) -> float:
preference_score = 0

for group in groups:
scores = [student.get_score() for student in group]
scores = []
if group[0].experiment:
for i, student1 in enumerate(group):
for student2 in group[i+1:]:
scores.append(student1.get_score(student2))
else:
scores = [student.get_score() for student in group]

if len(scores) > 1: # סטיית תקן מוגדרת רק עבור יותר מנתון אחד
diversity = statistics.stdev(scores)
else:
Expand Down Expand Up @@ -173,7 +141,6 @@ def abc_algorithm_with_prefrences(students: List[Student], num_groups: int, num_
if best_fitness > GLOBAL_MAX_VAL:
GLOBAL_MAX_VAL = best_fitness
GLOBAL_MAX = solutions[scores.index(GLOBAL_MAX_VAL)]
print(f"Iteration {iteration + 1}, Best Fitness: {GLOBAL_MAX_VAL}")

# בסוף, מחזירים את הפתרון הטוב ביותר
return GLOBAL_MAX
3 changes: 0 additions & 3 deletions ABC/StandardABC.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,6 @@ def abc_algorithm(students: List[Student], num_groups: int, num_iterations: int
solutions[i] = new_sol
scores[i] = calculate_diversity(new_sol)
stagnation[i] = 0
# הדפסת מידע על הדור
best_fitness = max(scores)
print(f"Iteration {iteration + 1}, Best Fitness: {best_fitness}")

# בסוף, מחזירים את הפתרון הטוב ביותר
best_index = scores.index(max(scores))
Expand Down
62 changes: 13 additions & 49 deletions Genetic/PreferencesGenetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,7 @@
import heapq
from typing import List, Tuple
from utils.student import Student

def initialize_groups(students: List[Student], num_groups: int) -> List[List[Student]]:
"""
הערה מקורית: יוצר קבוצות התחלתיות
מה הוספנו? לא ביצענו באופן אקראי לחלוטין, אנחנו מתאימים תלמידים לפי התאמות אישיות
"""
# ניצור את הקבוצות ריקות
groups = [[] for _ in range(num_groups)]
# נשמור תלמידים שהכנסנו לקבוצה
assigned_students = set()

# נחשב את הגודל המקסימלי של כל קבוצה
max_group_size = len(students) // num_groups + (1 if len(students) % num_groups != 0 else 0)

random.shuffle(students)
# נרוץ על כל תלמיד, ננסה להכניס אותו לקבוצה רק אם יש לו את אחת מהעדפות שלו בקבוצה
# ואם הקבוצה לא עברה את הגודל המקסימלי
for student in students:
for group in groups:
if len(group) < max_group_size and any(preference in [s.id for s in group] for preference in student.preferences):
group.append(student)
assigned_students.add(student.id)
break

# אם התלמיד לא הוכנס לאף קבוצה, נכניס אותו לקבוצה הכי קטנה (אם היא לא מלאה)
if student.id not in assigned_students:
smallest_group = min(groups, key=len)
if len(smallest_group) < max_group_size:
smallest_group.append(student)
assigned_students.add(student.id)

# נרוץ על התלמידים שעוד לא הוכנסו לקבוצות, כל אחד מהם בתורו נכניס לקבוצה הקטנה ביותר (אם היא לא מלאה)
remaining_students = [student for student in students if student.id not in assigned_students]
for student in remaining_students:
for group in groups:
if len(group) < max_group_size:
group.append(student)
break

return groups
from components.InitialDivision import initialize_groups

def calculate_diversity(groups: List[List[Student]]) -> float:
"""
Expand All @@ -53,7 +14,14 @@ def calculate_diversity(groups: List[List[Student]]) -> float:
preference_score = 0

for group in groups:
scores = [student.get_score() for student in group]
scores = []
if group[0].experiment:
for i, student1 in enumerate(group):
for student2 in group[i+1:]:
scores.append(student1.get_score(student2))
else:
scores = [student.get_score() for student in group]

if len(scores) > 1: # סטיית תקן מוגדרת רק עבור יותר מנתון אחד
diversity = statistics.stdev(scores)
else:
Expand All @@ -75,13 +43,13 @@ def calculate_diversity(groups: List[List[Student]]) -> float:
total_score = mean_diversity + preference_score - diversity_variance
return total_score

def generate_initial_population(students: List[Student], num_groups: int, population_size: int) -> List[List[List[Student]]]:
def generate_initial_population(students: List[Student], num_groups: int) -> List[List[List[Student]]]:
"""
יוצרת אוכלוסייה ראשונית של פתרונות.
כל פתרון הוא חלוקה של התלמידים לקבוצות.
"""
population = [] # רשימת פתרונות
for _ in range(population_size):
for _ in range(num_groups):
groups = initialize_groups(students, num_groups) # חלוקה אקראית
population.append(groups)
return population
Expand Down Expand Up @@ -172,9 +140,9 @@ def update_population(population: List[List[List[Student]]], fitness_scores: Lis
population[worst_index] = child
fitness_scores[worst_index] = child_fitness

def genetic_algorithm_with_preferences(students: List[Student], num_groups: int, population_size: int, generations: int, mutation_rate: float):
def genetic_algorithm_with_preferences(students: List[Student], num_groups: int, generations: int, mutation_rate: float):
# יצירת אוכלוסייה ראשונית
population = generate_initial_population(students, num_groups, population_size)
population = generate_initial_population(students, num_groups)
fitness_scores = calculate_population_fitness(population)

for generation in range(generations):
Expand All @@ -188,10 +156,6 @@ def genetic_algorithm_with_preferences(students: List[Student], num_groups: int,
# עדכון האוכלוסייה
update_population(population, fitness_scores, mutated_child)

# הדפסת מידע על הדור
#best_fitness = max(fitness_scores)
#print(f"Generation {generation + 1}, Best Fitness: {best_fitness}")

# מחזירים את הפתרון הטוב ביותר
best_index = fitness_scores.index(max(fitness_scores))
return population[best_index]
4 changes: 0 additions & 4 deletions Genetic/StandardGenetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,6 @@ def genetic_algorithm(students: List[Student], num_groups: int, population_size:
# עדכון האוכלוסייה
update_population(population, fitness_scores, mutated_child)

# הדפסת מידע על הדור
best_fitness = max(fitness_scores)
print(f"Generation {generation + 1}, Best Fitness: {best_fitness}")

# מחזירים את הפתרון הטוב ביותר
best_index = fitness_scores.index(max(fitness_scores))
return population[best_index]
153 changes: 153 additions & 0 deletions components/InitialDivision.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
from collections import deque
from typing import List, Dict, Any
from utils.student import Student
from random import shuffle

class Node:
__slots__ = ['data', 'neighbors', 'd', 'f', 'pie', 'color', 'student']

def __init__(self, data=None, student=None):
self.data = data
self.student = student
self.neighbors = []
self.d = None
self.f = None
self.pie = None
self.color = None # "White", "Gray", "Black", or "Red" (when assigned)

def __repr__(self):
return f"Node({self.data})"

def __gt__(self, other):
return self.data > other.data

def __lt__(self, other):
return self.data < other.data

def __eq__(self, other):
return self.data == other.data

def __hash__(self):
return hash(self.data)

def DFS(nodes: Dict[Any, Node]) -> None:
time = 0

def DFS_VISIT(u: Node):
nonlocal time
u.color = "Gray"
time += 1
u.d = time
for v in u.neighbors:
if v.color == "White":
v.pie = u
DFS_VISIT(v)
time += 1
u.f = time
u.color = "Black"

# Initialize all nodes for DFS
for node in nodes.values():
node.pie = None
node.color = "White"
# Run DFS from each unvisited node
for node in nodes.values():
if node.color == "White":
DFS_VISIT(node)

def is_valid_group(group: List[Node]) -> bool:
"""
Check that for every node in the group, at least one neighbor (preference) is also in the group.
"""
s = set(group)
for node in group:
if not any(n in s for n in node.neighbors):
return False
return True

def get_group(node: Node, group_size: int, partial: List[Node] = None, depth: int = 0, limit: int = 100) -> List[Node]:
"""
Recursively attempt to build a group starting at 'node'.
The group must be of size group_size and satisfy the preference condition.
Added depth parameter to limit excessive recursion.
"""
if depth > limit:
return []

if partial is None:
partial = []
current_group = partial + [node]

# If we reached the target group size, check if the group is valid
if len(current_group) == group_size:
return current_group if is_valid_group(current_group) else []

# Then try each neighbor that is not yet assigned and not already in the group.
for nbr in node.neighbors:
if nbr.color != "Red" and nbr not in current_group:
result = get_group(nbr, group_size, current_group, depth + 1, limit)
if result:
return result
return []

def group_ungrouped_nodes(ungrouped: List[Node], group_size: int) -> List[List[Node]]:
"""
For nodes that were not grouped by get_group, create groups of group_size.
This fallback method does not re-check the preference condition.
"""
groups = []
while ungrouped:
# Take the first group_size nodes (or whatever remains)
group = ungrouped[:group_size]
for node in group:
node.color = "Red"
groups.append(group)
ungrouped = ungrouped[group_size:]
return groups

def dfs_grouping(students: List[Student], num_groups: int) -> List[List[Node]]:
# Create nodes for each student
nodes: Dict[Any, Node] = {student.id: Node(student.id, student) for student in students}
# Build a dictionary of student preferences
preferences: Dict[Any, List[Any]] = {student.id: student.preferences for student in students}
# Build the graph: for each student, add neighbors based on preferences
for student in students:
for pref in preferences[student.id]:
if pref in nodes:
nodes[student.id].neighbors.append(nodes[pref])
# Run DFS over the nodes to set discovery times and parent pointers
DFS(nodes)

# Create a deque sorted by discovery time (largest d first)
Q = deque(sorted(nodes.keys(), key=lambda key: nodes[key].d if nodes[key].d is not None else 0, reverse=True))
groups = []
n = len(students)
target_size = n // num_groups

# Build groups by attempting to form a valid group starting from each node
while Q:
current = nodes[Q.popleft()]
if current.color != "Red":
group = get_group(current, target_size, limit=n)
if group:
for node in group:
node.color = "Red"
groups.append(group)

# Process any nodes that have not been assigned
ungrouped_nodes = [node for node in nodes.values() if node.color != "Red"]
# Calculate maximum group size (if not perfectly divisible)
max_group_size = target_size + (1 if n % num_groups != 0 else 0)
groups += group_ungrouped_nodes(ungrouped_nodes, max_group_size)

return groups

def initialize_groups(students: List[Student], num_groups: int) -> List[List[Student]]:
"""
Shuffle the students and create initial groups based on their preferences.
"""
shuffle(students)
node_groups = dfs_grouping(students, num_groups)
# Convert groups of Nodes back to groups of Student objects
student_groups = [[node.student for node in group] for group in node_groups]
return student_groups
Empty file added experiments/ABC/.gitkeep
Empty file.
Empty file added experiments/Genetic/.gitkeep
Empty file.
Loading