-
Notifications
You must be signed in to change notification settings - Fork 342
[WIP] NUSMods Optimizer Prototyping #3296
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
frizensami
wants to merge
57
commits into
nusmodifications:master
Choose a base branch
from
frizensami:optimizer
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
57 commits
Select commit
Hold shift + click to select a range
6daeea0
Add optimizer button, placeholder component, state
frizensami 39ae230
Add props to timetableoptimizer
frizensami c18487a
Run format
frizensami 3d3407c
Merge branch 'master' into optimizer
frizensami 6858299
Add precompiled Z3 WASM file + Emscripten wrapper
frizensami 2642863
Add Z3 WebWorker code and message passing types
frizensami 0fb26c0
Modify z3 types filename
frizensami c7dfbd1
Add callback defs to z3 types
frizensami edfbbe2
Remove log from z3w.js
frizensami 69d7376
Add worker-loader package to load Z3 WebWorker
frizensami 298d179
Update imports and typedefs in z3Worker
frizensami e4a23cc
MVP for initializing Z3 with worker-loader
frizensami 8b83870
Fix initialization messages for z3
frizensami b92f711
Add a temporary alert for z3 init
frizensami 3ffd9e6
Refactor files and rename for minimal example
frizensami c212883
Add sexpr-plus package for parsing Z3 output
frizensami 75ad08e
Add smtlib-ext lib, creates SMT-LIB code for Z3
frizensami a01d69f
Implementation of MVP optimizer utils stack
frizensami cc548b1
Fix incorrect callback type
frizensami befafca
Wire up button to run optimizer, fix varname bug
frizensami 0ddb1c7
Change lessons based on optimizer output
frizensami c5bb543
Merge branch 'master' into optimizer
frizensami 62f0acc
Fix some eslint errors
frizensami 43c8927
Update worker-loader to devDependencies
frizensami 17529bb
Fix majority of eslint errors
frizensami 212cf22
Fix webworker eslint errors
frizensami 4b2cfc2
Fix inline webpack loader eslint error
frizensami 7594da1
Correctly name constraint booleans
frizensami 3fb9d34
Z3Message -> Z3WorkerMessage for clarity
frizensami c6104c2
Remove unnecessary export
frizensami 392302a
Comments for WeekSolver, remove StringIdGenerator
frizensami b1b1abb
Change slot whoId to ownerId for naming clarity
frizensami cfd1ad0
Add doc comments for timetable solver
frizensami caabee3
Change SExpr to SNode in solver for accuracy
frizensami d9153ab
Publish smtlib-ext to remove dep on github
frizensami 66456ff
Add and apply vendor types for sexpr-plus
frizensami 525d604
Remove all remaining eslint errors
frizensami fdb4972
Solve typing issues except webworker Z3
frizensami de387b2
Merge branch 'master' into optimizer
frizensami 947dffe
Just include webworker lib
frizensami 2ba7c34
Add tests for z3weeksolver
frizensami 41c09a0
Ignore type errors for wasm
frizensami 2527a48
Increase test coverage for z3 timetable solver
frizensami 54ea81e
Add test coverage for setBooleanSelectorCosts
frizensami ccc5497
Autofix eslint
frizensami 5809287
Full test coverage for timetable solver
frizensami fe120de
Change test -> it for consistency
frizensami 513cd21
Add mocks and some tests for timetableOptimizer
frizensami d839ae4
Complete test coverage for timetableOptimizer
frizensami 72efcc4
Fix remaining test coverage for optimizer
frizensami 81119bf
Ignore webworkers in codecov
frizensami 3d50400
Partial commit for converter
frizensami 7ff3b04
Fix errors to see converter coverage
frizensami 4f6857a
Test coverage for converter
frizensami 2e75c66
Merge branch 'master' into optimizer
frizensami 952ff8c
Merge branch 'optimizer' of github.com:frizensami/nusmods into optimizer
frizensami 978acb1
Fix merge conflict marker
frizensami File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export const TOGGLE_OPTIMIZER_DISPLAY = 'TOGGLE_OPTIMIZER_DISPLAY' as const; | ||
export function toggleOptimizerDisplay() { | ||
return { | ||
type: TOGGLE_OPTIMIZER_DISPLAY, | ||
payload: null, | ||
}; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { OptimizerState } from 'types/reducers'; | ||
import { Actions } from 'types/actions'; | ||
|
||
import { TOGGLE_OPTIMIZER_DISPLAY } from 'actions/optimizer'; | ||
|
||
export const defaultOptimizerState: OptimizerState = { | ||
isOptimizerShown: false, | ||
}; | ||
|
||
function optimizer(state: OptimizerState = defaultOptimizerState, action: Actions): OptimizerState { | ||
switch (action.type) { | ||
case TOGGLE_OPTIMIZER_DISPLAY: | ||
return { | ||
...state, | ||
isOptimizerShown: !state.isOptimizerShown, | ||
}; | ||
default: | ||
return state; | ||
} | ||
} | ||
|
||
export default optimizer; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
// Intermediate types used to contain timetable information relevant to the optimizer | ||
import { mapValues, groupBy } from 'lodash'; | ||
import { Module, RawLesson, StartTime, EndTime } from 'types/modules'; | ||
import { SemTimetableConfig } from 'types/timetables'; | ||
|
||
// {module id}__{lesson type}__{lesson id} e.g., CS3203__Lecture__1 | ||
export type UniqueLessonString = string; | ||
export type Z3LessonID = number; | ||
|
||
/** | ||
* Main input format into the optimizer layers | ||
* */ | ||
export type OptimizerInput = { | ||
moduleInfo: ModuleInfoWithConstraints[]; | ||
constraints: GlobalConstraints; | ||
}; | ||
|
||
/** | ||
* Callbacks to communicate with the caller of TimetableOptimizer | ||
* */ | ||
export interface OptimizerCallbacks { | ||
onOptimizerInitialized(): void; | ||
onSmtlib2InputCreated(s: string): void; | ||
onSmtLib2ResultOutput(s: string): void; | ||
onTimetableOutput(timetable: OptimizerOutput): void; | ||
} | ||
|
||
/** | ||
* Final timetable output to the optimizer caller | ||
* */ | ||
export type OptimizerOutput = { | ||
isSat: boolean; | ||
timetable: SemTimetableConfig; | ||
}; | ||
|
||
/** | ||
* Modules for optimizer to consider. | ||
* required is a constraint indicating if the module can be dropped to fulfil other constraints | ||
* */ | ||
export type ModuleInfoWithConstraints = { | ||
mod: Module; | ||
required: boolean; | ||
lessonsGrouped: LessonsByGroupsByClassNo; | ||
}; | ||
|
||
// Mapping between lesson types -> classNo -> lessons. | ||
// We have to take one classNo of each lessonType, so this indicates all the slots to be filled | ||
// per classNo per lessonType | ||
export type LessonsByGroupsByClassNo = { | ||
[lessonType: string]: LessonsForLessonType; | ||
}; | ||
|
||
export type LessonsForLessonType = { [classNo: string]: readonly RawLesson[] }; | ||
|
||
/* | ||
* A list of times that are assigned to a particular owner (e.g., a lesson) | ||
* Used throughout optimizer to indicate that a particular block of time should be reserved if a owner id is chosen | ||
*/ | ||
export interface SlotConstraint { | ||
startEndTimes: Array<[number, number]>; // Array of start and end times as integers | ||
ownerId: number; // Numeric ID of owner, since we will encode this as an integer constraint | ||
ownerString: string; // string representing the owner: user-interpretable, used for varnames | ||
} | ||
|
||
/* | ||
* Indicating that a varname (boolean selector) has a cost attached to it if it is chosen. | ||
*/ | ||
export interface WorkloadCost { | ||
varname: string; | ||
cost: number; | ||
} | ||
|
||
// User-selected constraints to pass to optimizer | ||
export interface GlobalConstraints { | ||
// Min/max number of MCs + whether the constraint is active | ||
isWorkloadActive: boolean; | ||
minWorkload: number; | ||
maxWorkload: number; | ||
// Find exactly N free days + whether the constraint is active | ||
isFreeDayActive: boolean; | ||
numRequiredFreeDays: number; | ||
// Force these exact free days + whether the constraint is active | ||
isSpecificFreeDaysActive: boolean; | ||
specificFreeDays: Array<string>; | ||
// When lessons should start and end + whether the constraint is active | ||
isTimeConstraintActive: boolean; | ||
earliestLessonStartTime: StartTime; | ||
latestLessonEndTime: EndTime; | ||
// The hours where a lunch break should be allocated, | ||
// how many half-hour slots to allocate, and whether the constraint is active | ||
isLunchBreakActive: boolean; | ||
lunchStart: StartTime; | ||
lunchEnd: EndTime; | ||
lunchHalfHours: number; | ||
// Ask optimizer to compact timetable to leave as few gaps between lessons as possible | ||
isPreferCompactTimetable: boolean; | ||
} | ||
|
||
/** | ||
* Defs for communicating between Optimizer <-> WebWorker <-> WASM wrapper | ||
* */ | ||
// Need to disable since there's an eslint bug with enums | ||
// eslint-disable-next-line no-shadow | ||
export enum Z3WorkerMessageKind { | ||
// Request to init | ||
INIT = 'INIT', | ||
// Z3 initialized | ||
INITIALIZED = 'INITIALIZED', | ||
// Run the optimizer | ||
OPTIMIZE = 'OPTIMIZE', | ||
// Print output | ||
PRINT = 'PRINT', | ||
// Error | ||
ERR = 'ERR', | ||
// Z3 finished runnung | ||
EXIT = 'EXIT', | ||
// Z3 aborted | ||
ABORT = 'ABORT', | ||
} | ||
|
||
/** | ||
* Message to be sent back and forth between a Z3 webworker and any callers | ||
* */ | ||
export interface Z3WorkerMessage { | ||
kind: Z3WorkerMessageKind; | ||
msg: string; | ||
} | ||
|
||
// TODO Shouldn't be here | ||
export const defaultConstraints: GlobalConstraints = { | ||
isWorkloadActive: false, | ||
minWorkload: 0, | ||
maxWorkload: 30, | ||
isFreeDayActive: false, | ||
numRequiredFreeDays: 1, | ||
isSpecificFreeDaysActive: false, | ||
specificFreeDays: [], | ||
earliestLessonStartTime: '0800', | ||
latestLessonEndTime: '2200', | ||
lunchStart: '1100', | ||
lunchEnd: '1500', | ||
lunchHalfHours: 2, | ||
isLunchBreakActive: false, | ||
isTimeConstraintActive: false, | ||
isPreferCompactTimetable: false, | ||
}; | ||
|
||
/** | ||
* TODO move to utils | ||
* Transforms a module's lessons into a mapping from | ||
* lessonType ==> (classNo ==> list of lessons) | ||
* The optimizer cares that a classNo contains all the slots that should be filled. | ||
* */ | ||
export function lessonByGroupsByClassNo(lessons: readonly RawLesson[]): LessonsByGroupsByClassNo { | ||
const lessonByGroups: { [lessonType: string]: readonly RawLesson[] } = groupBy( | ||
lessons, | ||
(lesson) => lesson.lessonType, | ||
); | ||
const lessonByGroupsByClassNumber = mapValues( | ||
lessonByGroups, | ||
(lessonsOfSamelessonType: readonly RawLesson[]) => | ||
groupBy(lessonsOfSamelessonType, (lesson) => lesson.classNo), | ||
); | ||
return lessonByGroupsByClassNumber; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
declare module 'sexpr-plus' { | ||
// Parses string into expressions | ||
export function parse(input: string): Expr; | ||
// Location of start or end of an expression | ||
export type ExprLocation = { | ||
offset: number; | ||
line: number; | ||
column: number; | ||
}; | ||
// Location information for expressions | ||
export type ExprLocations = { | ||
start: ExprLocation; | ||
end: ExprLocation; | ||
}; | ||
// Base output type from parsing | ||
export type ExprNode = { | ||
type: string; | ||
content: Expr; | ||
location: ExprLocations; | ||
}; | ||
export type Expr = string | ExprNode[]; | ||
// try-catch with instanceof during parsing | ||
export interface SyntaxError { | ||
message: string; | ||
expected: string; | ||
found: string; | ||
location: ExprLocations; | ||
name: string; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
declare module '*.worker.ts' { | ||
// You need to change `Worker`, if you specified a different value for the `workerType` option | ||
class WebpackWorker extends Worker { | ||
constructor(); | ||
} | ||
|
||
// Uncomment this if you set the `esModule` option to `false` | ||
// export = WebpackWorker; | ||
export default WebpackWorker; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export const mockOnmessage = jest.fn(); | ||
export const mockPostMessage = jest.fn(); | ||
|
||
const WebpackWorker = jest.fn().mockImplementation(() => ({ | ||
onmessage: mockOnmessage, | ||
postMessage: mockPostMessage, | ||
})); | ||
|
||
export default WebpackWorker; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export const mockOnmessage = jest.fn(); | ||
export const mockPostMessage = jest.fn(); | ||
|
||
const WebpackWorker = jest.fn().mockImplementation(() => ({ | ||
onmessage: mockOnmessage, | ||
postMessage: mockPostMessage, | ||
})); | ||
|
||
export default WebpackWorker; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { invert } from 'lodash'; | ||
import { WorkingDays } from 'types/modules'; | ||
|
||
export const DAYS = WorkingDays.length; // One week mon - sat | ||
export const DAY_START_HOUR = 8; // This just needs to be earlier than the earliest lesson | ||
export const DAY_END_HOUR = 22; // Similarly, later than the latest lesson. | ||
export const HOURS_PER_DAY = DAY_END_HOUR - DAY_START_HOUR; // 14 8 am --> 10 pm. We save 24 - HOURS_PER_DAY variables per day. | ||
export const HOURS_PER_WEEK = HOURS_PER_DAY * DAYS; | ||
export const NUM_WEEKS = 13; | ||
|
||
export const DAY_IDXS: Record<string, number> = { | ||
monday: 0, | ||
tuesday: 1, | ||
wednesday: 2, | ||
thursday: 3, | ||
friday: 4, | ||
saturday: 5, | ||
}; | ||
export const IDX_DAYS = invert(DAY_IDXS); | ||
|
||
// All possible week numbers we should encounter (except WeekRange) | ||
export const ALL_WEEKS = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]]; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.