Skip to content
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
2ee4a33
Temp
Jan 20, 2024
336191c
Merge branch 'main' into very-cool-scenario-that-we-benchmark
Jan 20, 2024
d4e3d9f
Temp
Jan 20, 2024
8a00103
Timeslot
Jan 20, 2024
9f386ff
Timeslot
Jan 21, 2024
39e8283
Timeslot
Jan 21, 2024
787170b
Timeslot
Jan 21, 2024
9d10bf6
Project Scenario
Jan 21, 2024
1979597
Fix
Jan 21, 2024
0ed2064
Fix
Jan 21, 2024
75b1df3
Fix, add, modify a bunch of stuff
Jan 23, 2024
01ce852
Add stuff
Jan 23, 2024
653fdaf
Lint
Jan 23, 2024
4ab0069
Fix
Jan 23, 2024
3538fbe
Merge branch 'main' into very-cool-scenario-that-we-benchmark
Jan 24, 2024
ff8fa89
Fixed old run
Jan 24, 2024
253170d
Lint
Jan 24, 2024
b99143e
Minor
Jan 24, 2024
67f79a9
data
justino599 Jan 24, 2024
cf69ddb
Merge branch 'main' into very-cool-scenario-that-we-benchmark
Jan 24, 2024
c5d0f64
Merge branch 'very-cool-scenario-that-we-benchmark' of github.com:Tea…
Jan 24, 2024
9d80178
Fix group-matcher assignment and more data
Jan 24, 2024
3d0e58a
Merge branch 'main' into very-cool-scenario-that-we-benchmark
Jan 24, 2024
61bf694
Remove all, fair well
Jan 24, 2024
8e629f5
Sad sim cache and sad race
justino599 Jan 25, 2024
199faf3
Merge main
Jan 26, 2024
da4d3c9
Stuff
Jan 27, 2024
f070678
Add solo status metric
Jan 27, 2024
ffe6cd9
Lint
Jan 27, 2024
b9d8db6
Data
justino599 Jan 28, 2024
0d4a967
Merge branch 'very-cool-scenario-that-we-benchmark' of https://github…
justino599 Jan 28, 2024
becd75c
Merge branch 'main' into solo-status-metric
Jan 28, 2024
7d07baf
Merge branch 'main' into very-cool-scenario-that-we-benchmark
Jan 28, 2024
4d9179f
Merge branch 'solo-status-metric' into very-cool-scenario-that-we-ben…
Jan 29, 2024
b8d8e54
Merge branch 'main' into solo-status-metric
Jan 30, 2024
e7cceef
Fix solo status
Jan 30, 2024
72bcd95
Run time issue with group matcher
Jan 30, 2024
9f2d633
cool
justino599 Jan 30, 2024
2a2cf0f
Some cool stuff
justino599 Jan 30, 2024
a2ba5ec
Merge branch 'very-cool-scenario-that-we-benchmark' of https://github…
justino599 Jan 30, 2024
24fe89b
Data
justino599 Jan 31, 2024
74ad278
Group matcher stuff
Jan 31, 2024
d4857af
Data
Jan 31, 2024
b64ed81
Run the other too
Jan 31, 2024
9cc0fe0
Allow filtering of cosine similarity to specific attributes
justino599 Jan 31, 2024
4eb46b0
Merge branch 'main' into very-cool-scenario-that-we-benchmark
justino599 Feb 1, 2024
13416c2
Merge remote-tracking branch 'origin/357-cosine-similarity-metric-sho…
justino599 Feb 1, 2024
edbf0a0
1000 data
justino599 Feb 1, 2024
3aa3517
Cool
Feb 1, 2024
b0d21b1
Cool
Feb 1, 2024
77bde62
Cool
Feb 1, 2024
4e7a892
data
Feb 1, 2024
18794e8
Cool
Feb 1, 2024
f3d7819
Data
justino599 Feb 1, 2024
f4b0007
DAta
Feb 1, 2024
d4705f8
Data
justino599 Feb 1, 2024
f634d10
Merge branch 'very-cool-scenario-that-we-benchmark' of https://github…
justino599 Feb 1, 2024
f52f775
Cool
Feb 1, 2024
ccec7e1
Merge cloud
Feb 2, 2024
112f401
Run group matcher here
Feb 2, 2024
8fbf615
timeslot data
justino599 Feb 2, 2024
a45ca7b
Extra custom models
Feb 2, 2024
99bda45
Merge remote-tracking branch 'origin/very-cool-scenario-that-we-bench…
Feb 2, 2024
227d54b
Extra custom models
Feb 2, 2024
e59b0d0
Cool
Feb 2, 2024
47b4ed7
Group matcher data
Feb 2, 2024
21779ff
Merge branch 'very-cool-scenario-that-we-benchmark' of github.com:Tea…
Feb 2, 2024
f720b21
fix not parallel
Feb 2, 2024
e50082c
New scenario
Feb 2, 2024
e6405a2
New scenario that can be run on different machine
Feb 3, 2024
f41762e
lab computer changes
justino599 Feb 3, 2024
827bff5
class size 500 data
Feb 3, 2024
a3cca7c
Data
Feb 3, 2024
5a4033a
Merge branch 'very-cool-scenario-that-we-benchmark' of github.com:Tea…
Feb 3, 2024
accdda5
New data
Feb 3, 2024
41c7746
Weird
Feb 20, 2024
6fc7e5a
Merge main
Feb 28, 2024
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "api/ai/group_matcher_algorithm/group-matcher"]
path = api/ai/group_matcher_algorithm/group-matcher
url = https://github.com/ketphan02/group-matcher.git
9 changes: 9 additions & 0 deletions api/ai/algorithm_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
WeightAlgorithmConfig,
SocialAlgorithmConfig,
PriorityAlgorithmConfig,
GroupMatcherAlgorithmConfig,
)
from api.ai.interfaces.algorithm_options import (
RandomAlgorithmOptions,
Expand All @@ -16,6 +17,7 @@
MultipleRoundRobinAlgorithmOptions,
GeneralizedEnvyGraphAlgorithmOptions,
DoubleRoundRobinAlgorithmOptions,
GroupMatcherAlgorithmOptions,
)
from api.ai.interfaces.team_generation_options import TeamGenerationOptions
from api.ai.multiple_round_robin_with_adjusted_winner_algorithm.mrr_algorithm import (
Expand All @@ -28,6 +30,7 @@
from api.ai.double_round_robin_algorithm.double_round_robin_algorithm import (
DoubleRoundRobinAlgorithm,
)
from api.ai.group_matcher_algorithm.group_matcher_algorithm import GroupMatcherAlgorithm
from api.models.enums import AlgorithmType
from api.models.student import Student
from api.models.team_set import TeamSet
Expand Down Expand Up @@ -78,6 +81,8 @@ def get_algorithm_from_type(algorithm_type: AlgorithmType):
return GeneralizedEnvyGraphAlgorithm
if algorithm_type == AlgorithmType.DRR:
return DoubleRoundRobinAlgorithm
if algorithm_type == AlgorithmType.GROUP_MATCHER:
return GroupMatcherAlgorithm

raise NotImplementedError(
f"Algorithm type {algorithm_type} is not associated with an algorithm class!"
Expand All @@ -99,6 +104,8 @@ def get_algorithm_option_class(algorithm_type: AlgorithmType):
return GeneralizedEnvyGraphAlgorithmOptions
if algorithm_type == AlgorithmType.DRR:
return DoubleRoundRobinAlgorithmOptions
if algorithm_type == AlgorithmType.GROUP_MATCHER:
return GroupMatcherAlgorithmOptions

raise NotImplementedError(
f"Algorithm type {algorithm_type} is not associated with an algorithm options class!"
Expand All @@ -120,6 +127,8 @@ def get_algorithm_config_class(algorithm_type: AlgorithmType):
return None
if algorithm_type == AlgorithmType.DRR:
return None
if algorithm_type == AlgorithmType.GROUP_MATCHER:
return GroupMatcherAlgorithmConfig

raise NotImplementedError(
f"Algorithm type {algorithm_type} is not associated with an algorithm config class!"
Expand Down
Empty file.
1 change: 1 addition & 0 deletions api/ai/group_matcher_algorithm/group-matcher
Submodule group-matcher added at b10180
78 changes: 78 additions & 0 deletions api/ai/group_matcher_algorithm/group_matcher_algorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import csv
import os
from pathlib import Path
from typing import List

import pandas as pd

from api.ai.interfaces.algorithm import Algorithm
from api.ai.interfaces.algorithm_config import GroupMatcherAlgorithmConfig
from api.ai.interfaces.algorithm_options import GroupMatcherAlgorithmOptions
from api.ai.interfaces.team_generation_options import TeamGenerationOptions
from api.models.enums import ScenarioAttribute, fromAlRaceToRace, fromAlGenderToGender
from api.models.student import Student
from api.models.team import Team
from api.models.team_set import TeamSet


class GroupMatcherAlgorithm(Algorithm):
def __init__(
self,
algorithm_options: GroupMatcherAlgorithmOptions,
team_generation_options: TeamGenerationOptions,
algorithm_config: GroupMatcherAlgorithmConfig,
*args,
**kwargs,
):
super().__init__(algorithm_options, team_generation_options, algorithm_config)
self.csv_input_path = algorithm_config.csv_input_path
self.group_matcher_run_path = algorithm_config.group_matcher_run_path
self.outpath = Path.cwd() / "out-private.csv"
if self.outpath.exists():
self.outpath.unlink()
self.config_file_path = (
Path(self.group_matcher_run_path).parent / "example_config.py"
)

self.team_trace = {team.id: team for team_idx, team in enumerate(self.teams)}

if not self.csv_input_path.parent.exists():
self.csv_input_path.parent.mkdir(parents=True)

def prepare(self, students: List[Student]) -> None:
student_data = [student.to_group_matcher_data_format() for student in students]
with open(self.csv_input_path, "w") as csvfile:
writer = csv.DictWriter(
csvfile, fieldnames=student_data[0].keys(), delimiter=";"
)
writer.writeheader()
writer.writerows(student_data)

def generate(self, students: List[Student]) -> TeamSet:
# Run the group matcher algorithm
cmd = f"python3 {self.group_matcher_run_path} {self.config_file_path} {self.csv_input_path}"
cmd_output = os.system(cmd)

# Read the output csv file and create a TeamSet
df = pd.read_csv(self.outpath)
for _, row in df.iterrows():
new_student = Student(
_id=row["sid"],
name=row["first_name"] + " " + row["last_name"],
attributes={
ScenarioAttribute.YEAR_LEVEL.value: [int(row["year"])],
ScenarioAttribute.RACE.value: [
fromAlRaceToRace(int(row["race"])).value
],
ScenarioAttribute.GENDER.value: [
fromAlGenderToGender(int(row["gender"])).value
],
ScenarioAttribute.TIMESLOT_AVAILABILITY.value: list(
map(int, row["disc_times_options"].strip("[']").split(","))
),
},
)

self.team_trace[int(row["group_num"]) + 1].add_student(new_student)

return TeamSet(teams=self.teams)
34 changes: 30 additions & 4 deletions api/ai/interfaces/algorithm_config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable, Tuple, List

from api.models.student import Student
Expand Down Expand Up @@ -47,10 +48,10 @@ def validate(self):

@dataclass
class PriorityAlgorithmConfig(AlgorithmConfig):
MAX_KEEP: int = 3 # nodes
MAX_SPREAD: int = 3 # nodes
MAX_ITERATE: int = 1500 # iterations
MAX_TIME: int = 30 # seconds
MAX_KEEP: int = 15 # nodes
MAX_SPREAD: int = 30 # nodes
MAX_ITERATE: int = 30 # iterations
MAX_TIME: int = 10000000 # seconds

"""
Specifies the mutations as a list of [mutation_function, number_team_sets_generated_this_way]
Expand Down Expand Up @@ -86,19 +87,44 @@ def validate(self):
class MultipleRoundRobinAlgorithmConfig(AlgorithmConfig):
utility_function: Callable[[Student, TeamShell], float]

def __init__(self, utility_function: Callable[[Student, TeamShell], float]):
super().__init__()
self.utility_function = utility_function

def validate(self):
super().validate()


class DoubleRoundRobinAlgorithmConfig(AlgorithmConfig):
utility_function: Callable[[Student, TeamShell], float]

def __init__(self, utility_function: Callable[[Student, TeamShell], float]):
super().__init__()
self.utility_function = utility_function

def validate(self):
super().validate()


class GeneralizedEnvyGraphAlgorithmConfig(AlgorithmConfig):
utility_function: Callable[[Student, TeamShell], float]

def __init__(self, utility_function: Callable[[Student, TeamShell], float]):
super().__init__()
self.utility_function = utility_function

def validate(self):
super().validate()


class GroupMatcherAlgorithmConfig(AlgorithmConfig):
csv_input_path: Path
group_matcher_run_path: Path

def __init__(self, csv_output_path: Path, group_matcher_run_path: Path):
super().__init__()
self.csv_input_path = csv_output_path
self.group_matcher_run_path = group_matcher_run_path

def validate(self):
super().validate()
15 changes: 15 additions & 0 deletions api/ai/interfaces/algorithm_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,20 @@ def get_schema() -> Schema:
raise NotImplementedError


@dataclass
class GroupMatcherAlgorithmOptions(AlgorithmOptions):
def validate(self):
super().validate()

@staticmethod
def parse_json(_: Dict[str, Any]):
raise NotImplementedError

@staticmethod
def get_schema() -> Schema:
raise NotImplementedError


AnyAlgorithmOptions = Union[
RandomAlgorithmOptions,
WeightAlgorithmOptions,
Expand All @@ -303,4 +317,5 @@ def get_schema() -> Schema:
MultipleRoundRobinAlgorithmOptions,
GeneralizedEnvyGraphAlgorithmOptions,
DoubleRoundRobinAlgorithmOptions,
GroupMatcherAlgorithmOptions,
]
120 changes: 120 additions & 0 deletions api/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class AlgorithmType(Enum):
MRR = "multiple_round_robin_with_adjusted_winner"
GEG = "generalized_envy_graph"
DRR = "double_round_robin"
GROUP_MATCHER = "group_matcher"


class TokenizationConstraintDirection(Enum):
Expand Down Expand Up @@ -124,3 +125,122 @@ class PriorityType(Enum):
PROJECT_PREFERENCE = "project_preference"
PROJECT_REQUIREMENT = "project_requirement"
SOCIAL_PREFERENCE = "social_preference"


# From paper: https://sigcse2023.sigcse.org/details/sigcse-ts-2023-papers/163/Inclusive-study-group-formation-at-scale
class AlRace(AttributeValueEnum):
White = "White"
Asian = "Asian"
Hispanic = "Hispanic"
Black_Or_African_American = "Black/African American"
Indegenous = "Indegenous"
Middle_Eastern = "Middle-Eastern"
Multiple_Races = "Multiple races"


class AlYearLevel(AttributeValueEnum):
Freshman = "freshman"
Sophomore = "sophomore"
Junior = "junior"
Senior = "senior"
Graduate = "graduate"


class AlGender(AttributeValueEnum):
Female = "Female"
Male = "Male"
Other = "Other"


def fromGenderToAlGender(gender: Gender) -> AlGender:
if gender == Gender.MALE:
return AlGender.Male
if gender == Gender.FEMALE:
return AlGender.Female
return AlGender.Other


def fromAlGenderToGender(alGenderNum: int or str) -> Gender:
if alGenderNum == 0 or alGenderNum == "Male":
return Gender.FEMALE
if alGenderNum == 1 or alGenderNum == "Female":
return Gender.MALE
return Gender.OTHER


def fromRaceToAlRace(race: Race) -> AlRace:
if race == Race.European:
return AlRace.White
if (
race == Race.South_Asian
or race == Race.East_Asian
or race == Race.South_East_Asian
):
return AlRace.Asian
if race == Race.Hispanic_or_Latin_American:
return AlRace.Hispanic
if race == Race.African:
return AlRace.Black_Or_African_American
if race == Race.First_Nations_or_Indigenous:
return AlRace.Indegenous
if race == Race.Middle_Eastern:
return AlRace.Middle_Eastern
if race == Race.Other:
return AlRace.Multiple_Races


def fromAlRaceToRace(alRaceNum: int or str) -> Race:
if alRaceNum == 0 or alRaceNum == "White":
return Race.European
if alRaceNum == 1 or alRaceNum == "Asian":
return Race.South_Asian
if alRaceNum == 2 or alRaceNum == "Hispanic":
return Race.Hispanic_or_Latin_American
if alRaceNum == 3 or alRaceNum == "Black/African American":
return Race.African
if alRaceNum == 4 or alRaceNum == "Indegenous":
return Race.First_Nations_or_Indigenous
if alRaceNum == 5 or alRaceNum == "Middle-Eastern":
return Race.Middle_Eastern
if alRaceNum == 6 or alRaceNum == "Multiple races":
return Race.Other


def fromYearLevelToAlYearLevel(yearLevel: int) -> AlYearLevel:
if yearLevel == 0:
return AlYearLevel.Freshman
if yearLevel == 1:
return AlYearLevel.Sophomore
if yearLevel == 2:
return AlYearLevel.Junior
if yearLevel == 3:
return AlYearLevel.Senior
return AlYearLevel.Graduate


def fromAlYearLevelToYearLevel(alYearLevel: str) -> int:
if "freshman" in alYearLevel.lower():
return 0
if "sophomore" in alYearLevel.lower():
return 1
if "junior" in alYearLevel.lower():
return 2
if "senior" in alYearLevel.lower():
return 3
return 4


def fromNumbersToTimeSlots(numbers: List[int]) -> List[str]:
return [fromNumberToTimeslot(number) for number in numbers]


def fromNumberToTimeslot(number: int) -> str:
return str(number)


def fromTimeslotToNumber(timeslot: str) -> int:
return int(timeslot)


def fromTimeslotsToNumbers(timeslots: List[str]) -> List[int]:
return [fromTimeslotToNumber(timeslot) for timeslot in timeslots]
Loading