Skip to content

Commit 4ecc373

Browse files
Peedee2002jcongcjason
authored
Feat/autoplan courses (#1264)
* bump backend dependencies to latest * add guest auth for development environments * hook up autoplanning to the frontend * hook up autoplanning to the frontend * don't try to go to csesoc api during testing * don't try to go to csesoc api during testing * give typeguard for oidc config * add test flag * address nits * improve some waffling in autoplan.py * fix formatting * remove unnessesary complex logic around what the input should be - we always only plan unplanned courses * fix variable names around the use of channeled constraints * fix: remove equivalent and exclusion courses from being added by default (#1275) * Feat/autoplan courses (#1272) * bump backend dependencies to latest * add guest auth for development environments * hook up autoplanning to the frontend * hook up autoplanning to the frontend * don't try to go to csesoc api during testing * don't try to go to csesoc api during testing * give typeguard for oidc config * add test flag * address nits * improve some waffling in autoplan.py * fix formatting * remove unnessesary complex logic around what the input should be - we always only plan unplanned courses * fix variable names around the use of channeled constraints --------- Co-authored-by: Peter Derias <peter.derias@gmail.com> * fix: adding core courses automatically (#1274) * bump backend dependencies to latest * add guest auth for development environments * hook up autoplanning to the frontend * hook up autoplanning to the frontend * don't try to go to csesoc api during testing * don't try to go to csesoc api during testing * give typeguard for oidc config * add test flag * address nits * improve some waffling in autoplan.py * fix formatting * remove unnessesary complex logic around what the input should be - we always only plan unplanned courses * fix variable names around the use of channeled constraints * fix: allow toggling of adding default courses and fix adding of mutex courses --------- Co-authored-by: Peter Derias <peter.derias@gmail.com> Co-authored-by: jason <jasoncchen@bytedance.com> --------- Co-authored-by: Peter Derias <peter.derias@gmail.com> Co-authored-by: jason <jasoncchen@bytedance.com> * fix: repeated function definitions * fix lint error --------- Co-authored-by: jason <111541945+jcongc@users.noreply.github.com> Co-authored-by: jason <jasoncchen@bytedance.com> Co-authored-by: jason <jasoncongcchen@gmail.com>
1 parent fc93f07 commit 4ecc373

31 files changed

Lines changed: 754 additions & 270 deletions

File tree

.github/actions/setup-ci-env/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@ runs:
2323
--mongodb_password="github_ci"
2424
--mongodb_service_hostname="mongodb"
2525
shell: bash
26+
- name: Set backend test environment
27+
run: echo "ENVIRONMENT=test" >> env/backend.env
28+
shell: bash
2629

.pylintrc

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ load-plugins=
3939
# Pickle collected data for later comparisons.
4040
persistent=yes
4141

42-
# When enabled, pylint would attempt to guess common misconfiguration and emit
43-
# user-friendly hints instead of false-positive error messages.
44-
suggestion-mode=yes
45-
4642
# Allow loading of arbitrary C extensions. Extensions are imported into the
4743
# active Python interpreter and may run arbitrary code.
4844
unsafe-load-any-extension=no
@@ -624,6 +620,6 @@ min-public-methods=0
624620

625621
# Exceptions that will emit a warning when being caught. Defaults to
626622
# "BaseException, Exception".
627-
overgeneral-exceptions=BaseException,
628-
Exception
623+
overgeneral-exceptions=builtins.BaseException,
624+
builtins.Exception
629625

backend/algorithms/autoplanning.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,57 @@
55
from algorithms.objects.course import Course
66
from algorithms.objects.user import User
77
from ortools.sat.python import cp_model # type: ignore
8-
from server.routers.model import CONDITIONS
98

109
# Inspired by AbdallahS's code here: https://github.com/AbdallahS/planner
1110
# with help from Martin and MJ :)
1211

12+
# TODO (semester): remove hard coded 4 terms
1313
def terms_between(start: Tuple[int, int], end: Tuple[int, int]):
1414
return (end[0] - start[0]) * 4 + end[1] - start[1]
1515

1616
def map_var_to_course(courses: list[Course], var: cp_model.IntVar):
17-
return [course for course in courses if course.name == var.Name()][0]
17+
return [course for course in courses if course.name == var.name][0]
1818

1919
def map_course_to_var(course: Course, variables: list[cp_model.IntVar]):
20-
return [variable for variable in variables if course.name == variable.Name()][0]
20+
return [variable for variable in variables if course.name == variable.name][0]
2121

2222
def convert_to_term_year(number: int, start: Tuple[int, int]):
2323
return (number // 4 + start[0], number % 4 + start[1])
2424

2525
def autoplan(courses: list[Course], user: User, start: Tuple[int, int], end: Tuple[int, int], uoc_max: list[int]) -> list[Tuple[str, Tuple[int, int]]]:
2626
"""
2727
given a list of courses, we will fill our terms in a valid ordering.
28+
locked courses are included as fixed constraints.
2829
we will enforce that:
2930
- the course must be offered in that term
3031
- duplicate courses (usually only multiterm courses) must be taken consecutively
3132
- the UOC max for each term is adhered to (no overloading, and the user should be allowed to manipulate this)
3233
- the prerequisites are respected.
3334
"""
3435
# pylint: disable=too-many-locals
35-
# TODO: add a way to lock in courses
3636
model = cp_model.CpModel()
3737
# 1. enforces terms
38-
variables = [model.NewIntVarFromDomain(cp_model.Domain.FromIntervals(course.term_domain(start, end)), course.name) for course in courses]
38+
variables = [model.new_int_var_from_domain(cp_model.Domain.from_intervals(course.term_domain(start, end)), course.name) for course in courses]
3939
# 2. if any courses are named the same, then they must be taken consecutively
40-
possible_course_dupes = [course.name for course in courses if not course.locked]
40+
possible_course_dupes = [course.name for course in courses if course.locked is None]
4141
duplicate_courses = set(c for c in possible_course_dupes if possible_course_dupes.count(c) > 1)
4242
for dupe in duplicate_courses:
43-
matched_courses = [variable for variable in variables if variable.Name() == dupe]
43+
matched_courses = [variable for variable in variables if variable.name == dupe]
4444
for match, next_match in zip(matched_courses, matched_courses[1:]):
45-
model.Add(match + 1 == next_match)
45+
model.add(match + 1 == next_match)
4646

4747
# 3. set max UOC for a term
4848
for index, m in enumerate(uoc_max):
4949
boolean_indexes = []
5050
for v in variables:
5151
# b is a 'channeling constraint'. This is done to fill the resovoir only *if* the course is in that given term
5252
# https://developers.google.com/optimization/cp/channeling
53-
b = model.NewBoolVar('hi')
54-
model.Add(v == index).OnlyEnforceIf(b)
55-
model.Add(v != index).OnlyEnforceIf(b.Not())
53+
b = model.new_bool_var('hi')
54+
model.add(v == index).only_enforce_if(b)
55+
model.add(v != index).only_enforce_if(b.Not())
5656
boolean_indexes.append(b)
5757
# if the course is in term 'index', only allow 0 to m UOC to exist in that term.
58-
model.AddReservoirConstraintWithActive(
58+
model.add_reservoir_constraint_with_active(
5959
variables,
6060
list(map_var_to_course(courses, var).uoc for var in variables), # this fills the resovoir by UoC units if active
6161
boolean_indexes, # a course is only active if in term 'index'
@@ -65,7 +65,7 @@ def autoplan(courses: list[Course], user: User, start: Tuple[int, int], end: Tup
6565

6666
# 4. enforce prereqs, only if not locked by user
6767
for course in courses:
68-
if course.locked:
68+
if course.locked is not None:
6969
continue
7070

7171
# this is the responsibility of the condition class to generate prereq model.
@@ -76,13 +76,15 @@ def autoplan(courses: list[Course], user: User, start: Tuple[int, int], end: Tup
7676
map_course_to_var(course, variables)
7777
)
7878
solver = cp_model.CpSolver()
79-
status = solver.Solve(model)
79+
status = solver.solve(model)
8080
if status in [cp_model.MODEL_INVALID, cp_model.INFEASIBLE]:
8181
raise ValueError(f'your courses are impossible to put in these terms! Error code: {status}')
82-
return [(v.Name(), convert_to_term_year(solver.Value(v), start)) for v in variables]
82+
return [(variable.name, convert_to_term_year(solver.value(variable), start)) for variable in variables]
8383

8484

8585
if __name__ == '__main__':
86+
from server.routers.model import CONDITIONS
87+
8688
pprint(autoplan(
8789
[
8890
Course("MATH1141", CONDITIONS["MATH1141"], 65, 6, {2020: [1, 3], 2021: [1, 3], 2022: [1, 3]}),
@@ -98,7 +100,7 @@ def autoplan(courses: list[Course], user: User, start: Tuple[int, int], end: Tup
98100
Course("COMP2511", CONDITIONS["COMP2511"], 65, 6, {2020: [2, 3], 2021: [2, 3], 2022: [2, 3]}),
99101
Course("MATH1241", CONDITIONS["MATH1141"], 65, 6, {2020: [2, 3], 2021: [2, 3], 2022: [2, 3]}),
100102
Course("MATH3411", CONDITIONS["MATH3411"], 65, 6, {2020: [3], 2021: [3], 2022: [3]}),
101-
Course("COMP3411", CONDITIONS["COMP3411"], 65, 6, {2020: [3], 2021: [0], 2022: [0]}),
103+
Course("COMP3411", CONDITIONS["COMP3411"], 65, 6, {2020: [3], 2021: [3], 2022: [0]}),
102104
Course("COMP6841", CONDITIONS["COMP6841"], 65, 6, {2020: [1], 2021: [1], 2022: [1]}),
103105
Course("COMP3231", CONDITIONS["COMP3231"], 65, 6, {2020: [1], 2021: [1], 2022: [1]}),
104106
Course("COMP3141", CONDITIONS["COMP3141"], 65, 6, {2020: [2], 2021: [2], 2022: [2]}),

0 commit comments

Comments
 (0)