The CourseLoad
class is
one of the main access points to the API functionality.
A CourseLoad
represents
a course load (recall that this a choice of study session and a choice
of courses to take in that study session) as well as associated information
such as selected activity configurations for the course load and generated
timetables for these activity configurations. CourseLoad
contains methods that allow the user to interact with the course load,
such as by selecting courses, selecting activity configurations, generating
timetables, and sorting the timetables by various criteria. As such, CourseLoad
is one of the most important
classes in the API.
A CourseLoad
is associated
with a user session, and is created by calling NewCourseLoad()
or OpenCourseLoad()
on the UserSession
object representing the user session with which it is to be associated.
The CourseLoad
is freed
when its destructor is caled. Note that CourseLoad
is moveable but not copyable; user code can move it around as desired.
Here is an outline of the CourseLoad class:
class CourseLoad { public: // Nested classes struct IErrorHandler { enum class response { ignore, abort }; virtual response course_not_found(const string& activity_key) = 0; virtual response session_not_found(const Offering& offering) = 0; }; // API functions bool IsUpToDate() const; string GetFileName() const; void Save(const string& filename); course_load_state GetState() const; vector<StudySession> GetStudySessions() const; string GetCurrentStudySession() const; void SelectStudySession(size_t study_session_index); void ClearStudySession(); CourseList GetCoursesForSelectedStudySession() const; vector<Offerings> GetSelectedCourses() const; void SelectCourses(const vector<string>& activity_keys); void ClearSelectedCourses(); void SpecifyTerm(const string& activity_code, session_type term); bool GenerateConfigurations(); void UnspecifyTerms(); void SetConfigurations(const vector<ActivityConfiguration>& configs); size_t GetConfigCount() const; void SelectConfigurations(const vector<size_t>& configs); size_t GetSelectedConfigCount() const; size_t GetActivityCount() const; ActivityConfiguration GetConfig(size_t config_index) const; ActivityConfiguration GetSelectedConfig(size_t config_index) const; void ClearSelectedConfigurations(); void GenerateTimetables(ProgressIndicator progress_indicator, function<void(GenerationStatistics)> generation_stats_callback); vector<bool> GenerateTimetables(ProgressIndicator progress_indicator, function<void(GenerationStatistics)> generation_stats_callback, const vector<size_t>& timetable_limits); size_t GetTimetableCount(size_t config_index) const; const Timetable& GetTimetable(size_t config_index, size_t timetable_index) const; void ClearTimetables(); vector<pair<string, string>> GetConflicts(size_t config_index) const; timetable_sort_criteria GetCurrentSortCriteria(size_t config_index) const; void SortTimetables(size_t config_index, const timetable_sort_criteria& sort_criteria, ProgressIndicator progress_indicator); size_t GetMemoryUsagePerTimetable(size_t config_index) const; };
The interface IErrorHandler
allows clients to specify how various errors should be handled when
opening a course load file. An error can either be ignored (see below
for the specific semantics for each error), in which case opening of
the course load resumes, or aborted, in which case an exception is
thrown.
IErrorHandler
has one
method for each type of potentially-recoverable error that can occur
during the opening of a course load file. Every time a potentially-recoverable
error occurs, the appropriate method is called, with details about
the specific error (for example, the activity key of a course that
could not be found) passed as arguments to the call, and the method's
return value will determine how the error is handled.
The IErrorHandler
instance
on which the methods are called is passed in as an optional argument
to UserSession::OpenCourseLoad
().
The default value for this argument is an error handler which aborts
for all errors. To specify a different behaviour, clients should derive
from IErrorHandler
and pass in an object of their derived type to UserSession::OpenCourseLoad
().
Note that, since clients get details about the errors that occurred, they can implement a variety of end-user behaviours - for example, warning the user that a course in the course load was not found and showing the course code of the offending course.
struct IErrorHandler { enum class response { ignore, abort }; virtual response course_not_found(const string& activity_key) = 0; virtual response session_not_found(const Offering& offering) = 0; };
enum class response { ignore, abort };
An enumeration used to specify the desired response to an error when opening a course load file. The error can either be ignored (see below for the specific semantics for each error), or the error can cause the opening of the course load to be aborted.
virtual response course_not_found(const string& activity_key) = 0;
Called when a course specified in the course load file could not
be found in the timetable data. This can happen if all offerings
the course have been cancelled since the time the course load file
was saved. activity_key
is the activity key of the course that could not be found. If the
return value is response::ignored
,
the course is removed from the course load.
virtual response session_not_found(const Offering& offering) = 0;
Called when a session (offering) of a course specified in the course
load file could not be found in the timetable data. This can happen
if the offering has been cancelled since the time the course load
file was saved. If the return value is response::ignored
,
the configuration that the offering participated in is removed
from the course load.
bool CourseLoad::IsUpToDate() const;
Checks whether the course load is up to date; that is, whether any
modifications have been made to it since being created or being last
saved using Save()
.
string CourseLoad::GetFileName() const;
Get the filename that the course load was last saved as (using Save()
), or from which the course load was
loaded (if created using OpenCourseLoad()
).
The returned filename is empty if the course load was not loaded from a file and has not been saved.
void CourseLoad::Save(const string& path);
Save the course load to a file with the given path, in a plain text
format. If path
is
not absolute, it is interpreted as being relative to the client program's
current working directory. The exact format is not exposed by the API;
the file is meant to be opened later by OpenCourseLoad()
.
Note: only information about which study session, courses, and configurations
were selected is saved, not the actual timetables. The actual timetables
can be re-generated after the saved course load is opened with OpenCourseLoad()
.
Requires: GetState() == course_load_state::timetables_generated
course_load_state CourseLoad::GetState() const;
Return the current state of the course load.
See also: course_load_state
vector<StudySession> CourseLoad::GetStudySessions() const;
Returns a list of all study sessions. There will be one for each line in sessions.txt.
string CourseLoad::GetCurrentStudySession() const;
Return the name of the study session that is currently selected.
Requires: GetState() >= course_load_state::session_selected
void CourseLoad::SelectStudySession(size_t study_session_index);
Select the study session identified by the specified index (into the
list returned by GetStudySessions()
).
After calling this function, the course load state will be course_load_state::session_selected
.
Requires: GetState() == course_load_state::empty
void CourseLoad::ClearStudySession();
De-select the currently selected study session.
After calling this function, the course load state will be course_load_state::empty
.
Requires: GetState() == course_load_state::session_selected
CourseList CourseLoad::GetCoursesForSelectedStudySession() const;
Get all the courses available for the currently selected study session,
represented as a CourseList
.
Requires: GetState() >= course_load_state::session_selected
vector<Offerings> CourseLoad::GetSelectedCourses() const;
Get a list of the currently selected courses.
Requires: GetState() >= course_load_state::courses_selected
void CourseLoad::SelectCourses(const vector<string>& activity_keys);
Select the courses identified by the specified keys. The key should
come from the key
field
of the Offerings
structure for the course.
The course load state after this operation depends on whether or not
there are any half-courses that are offered in both terms among the
selected courses. If there are none, so that only one activity configuration
is possible for this course load, that one activity configuration is
automatically selected, and the course load state will be course_load_state::configs_selected
.
In this case, the next API function to call is GenerateTimetables()
. Otherwise, the course load state
will be course_load_state::courses_selected
and the next API function to call is GenerateConfigurations()
(optionally preceded by some calls
to SpecifyTerm()
).
To determine which of these courses of actions to take, call GetState()
after this operation and see what
it returns.
Requires: GetState() == course_load_state::session_selected
void CourseLoad::ClearSelectedCourses();
De-select all selected courses.
After this operation, the course load state willbe course_load_state::session_selected
Requires: GetState() == course_load_state::courses_selected
void CourseLoad::SpecifyTerm(const string& activity_code, session_type term);
Restrict the possible activity configurations that will be generated
when calling GenerateConfigurations()
to the ones where the course identified
by the specified activity code runs in the specified term. The course
in question must be a half-course that runs in both terms.
This operation does not change the course_load_state
,
but will affect which configurations are generated when calling GenerateConfigurations()
later.
Observe that by calling this function for every selected course that
is offered in both terms, the set of possible activity configurations
can be narrowed down to one; in this case, the subsequent call to
GenerateConfigurations()
will automatically select that one
configuration and advance the course load state to course_load_state::configs_selected
.
If generating timetables for a single activity configuration is desired,
then prefer specifying that activity configuration this way rather
than using SelectConfigurations()
.
Requires: GetState() == course_load_state::courses_selected
See also: Activity Configuration Fine Points
bool CourseLoad::GenerateConfigurations();
Generate the set of possible activity configurations for the course
load. The generated activity configurations will be available for examination
via GetConfigCount()
and GetConfig()
, and selection via SelectConfigurations()
.
When generating configurations, terms specified in any previous calls
to SpecifyTerm()
are taken into account; that is,
configurations where a course runs in a different term than the term
specified for that course by SpecifyTerm()
will not be generated.
Not all possible configurations are generated, only ones that are balanced
between the two semesters (where possible, subject to the constraints
imposed by half-courses that run only in one term, and half-courses
which run in both terms but for which we are only considering one of
the terms due to a previous call to SpecifyTerm()
). If unbalanced configurations are
desired, they can be specified using calls to SpecifyTerm()
. (If multiple unbalanced configurations
are desired, SetConfigurations()
can be used to specify them instead).
The course load state after this operation depends on the number of
configurations generated. If only one or two configurations were generated,
the generated configuration(s) are auto-selected and the course load
state is advanced to course_load_state::configs_selected
.
In this case, GenerateConfigurations()
returns true, and the next API function
to call is GenerateTimetables()
. If more than two configurations
were generated, the course load state remains course_load_state::courses_selected
,
GenerateConfigurations()
returns false, and the next API function
that should be called is SelectConfigurations()
.
Note: The rationale for auto-selecting the generated configuration
if only one configuration is generated is that that would be the only
choice to select anyways. The rationale for auto-selecting the generated
configurations if two configurations are generated is that the API
assumes that those configuration choices that can be expressed using
SpecifyTerm()
, have been expressed using SpecifyTerm()
prior to calling GenerateConfigurations()
. SelectConfigurations()
is meant only to make configuration
choices that cannot be expressed using SpecifyTerm()
. For example, if four half-courses
CSC148H1
, CSC165H1
, CHM138H1
,
and CHM139H1
are taken,
and you want to take any of them in either term , but subject to the
constraint that CHM138H1
and CHM139H1
cannot
be taken in the same term, this constraint cannot be expressed by any
calls to SpecifyTerm()
- instead you must call GenerateConfigurations()
without making any calls to SpecifyTerm()
, and then use SelectConfigurations()
to weed out the configurations where
CHM138H
and CHM139H1
run in the same term. In
the case where only two configurations are generated, it must be the
case that the two configurations differ only by the placement of a
half-course into one term or the other. If you had wanted a specific
term for this half course, you should have specified so using SpecifyTerm()
- since you didn't, the API assumes
that you want to take the course in either semester, and goes ahead
and auto-selects both configurations. In cases where more than two
configurations are generated, no such intent can be deduced, since
it could be a situation like the one with CHM138H1
/CHM139H1
.
Requires: GetState() == course_load_state::courses_selected
See also: Activity Configuration Fine Points
void CourseLoad::UnspecifyTerms();
Undo the effect of any calls made to SpecifyTerm()
for this course load. A subsequent
call to GenerateConfigurations()
will use both terms for all half-courses
that are offered in both terms (unless SpecifyTerm()
is called again before calling GenerateConfigurations()
).
Requires: GetState() == course_load_state::courses_selected
void CourseLoad::SetConfigurations(const vector<ActivityConfiguration>& configs);
Set the set of available activity configurations to the ones in configs
. This call undoes the effects
of any previous calls to SpecifyTerm()
or GenerateConfigurations()
.
This can be used to achieve selecting multiple unbalanced activity
configurations, something which is impossible using SpecifyTerm()
and GenerateConfigurations()
(see Activity
Configuration Fine Points for details).
Requires: GetState() == course_load_state::courses_selected
size_t CourseLoad::GetConfigCount() const;
Return the number of possible activity configurations for this course load.
Requires: GetState() >= course_load_state::courses_selected
and GenerateConfigurations()
or SetConfigurations()
has been called (or SelectCourses()
auto-generated and auto-selected
configurations).
void CourseLoad::SelectConfigurations(const vector<size_t>& configs);
Select the activity configurations identified by the specified indices for this course load.
configs
is a list of
indices into the list of possible activity configurations, which is
accessible via the functions GetConfigCount()
and GetConfig()
.
Timetables will be generated for the selected activity configurations
when calling GenerateTimetables()
.
After this operation, the course load state will be course_load_state::configs_selected
Requires: GetState() == course_load_state::courses_selected
and GenerateConfigurations()
or SetConfigurations()
has been called.
size_t CourseLoad::GetSelectedConfigCount() const;
Get the number of selected activity configurations for this course load.
Requires: GetState() >= course_load_state::configs_selected
size_t CourseLoad::GetActivityCount() const;
Get the number of selected courses for this course load.
Requires: GetState() >= course_load_state::courses_selected
ActivityConfiguration CourseLoad::GetConfig(size_t i) const;
Get the i
th activity
configuration for this course load. i
can range from 0 to GetConfigCount()
- 1.
Requires: GetState() >= course_load_state::courses_selected
and GenerateConfigurations()
or SetConfigurations()
has been called (or SelectCourses()
auto-generated and auto-selected
configurations).
ActivityConfiguration CourseLoad::GetSelectedConfig(size_t i) const;
Get the i
th selected
activity configuration for this course load. i
can range from 0 to GetSelectedConfigCount
- 1. Note that a configuration's index in the list of all configurations
(as accessible via GetConfigCount()
and GetConfig()
, and that same configuration's index
in the list of selected configurations
(as accessible via GetSelectedConfigCount()
and GetSelectedConfig()
, are not necessarily the same.
Requires: GetState() >= course_load_state::configs_selected
void CourseLoad::ClearSelectedConfigurations();
De-select all selected activity configurations for this course load.
After this operation, the course load state will be course_load_state::courses_selected
.
The list of possible configurations, as accessible via GetConfigCount()
and GetConfig
(),
is not cleared, and remains accessible (it can be re-generated with
different SpecifyTerm()
constraints if desired, by calling
UnspecifyTerms()
, making the calls to SpecifyTerm()
, and calling GenerateConfigurations()
again.
Requires: GetState() == course_load_state::configs_selected
void CourseLoad::GenerateTimetables(ProgressIndicator progress_indicator, function<void(GenerationStatistics)> generation_stats_callback);
vector<bool> CourseLoad::GenerateTimetables(ProgressIndicator progress_indicator, function<void(GenerationStatistics)> generation_stats_callback, const vector<size_t>& timetable_limits);
Generate all conflict-free timetables for all selected activity configurations
for this course load. The generated timetables will be accessible via
GetTimetableCount()
and GetTimetable()
.
Note that while for most course loads, the number of conflict-free timetables is relatively low (anywhere from one or two to a few hundred or a few thousand), there are some course loads for which the number of conflict-free timetables can be in the millions (typically these will be first-year life science course loads, where almost all of the courses have lecture, tutorial, and practical sections, with as many as 10 choices for each). The timetable generation algorithm is extremely efficient - capable of generating hundreds of thousands of timetables per second - but for these course loads, the generation process may take a user-noticeably long time. For this reason, two callbacks are provided that allow the generation algorithm to keep the caller updated about the status of the generation process.
The progress_indicator
callback is used to indicate the percentage completion of the generation
process. See ProgressIndicator
for details. Please note that due to the nature of the generation algorithm,
the percentage completion figure is just an estimate and may not increase
at a constant rate.
The generation_stats_callback
callback is used to indicate various other statistics about the generation
process, such as the number of timetables generated so far, the elapsed
time, and the average generation speed. Any function or function object
that is callable with a GenerationStatistics
argument can be used for this callback. Note that this callback will
be called relatively infrequently - about once every 20,000 generated
timetables. Its main purpose is to allow clients to inform their users
of the progress of the generation process in cases where the process
takes a user-noticeably long time.
If either callback throws an exception, the API will allow the thrown
exception to propagate out of the GenerateTimetables()
call and the course load state will
remain at course_load_state::configs_selected
,
with any timetables generated being discarded.
Otherwise, after the call completes the course load state will be
course_load_state::timetables_generated
.
The three-argument version allows specifying, for each selected activity
configuration, a limit on the number of timetables that will be generated
for that configuration. If a limit is specified, the number of timetables
generated for the configuration will be no greater than the limit.
If there are more valid timetables than the limit, it is not specified
which of the valid timetables are
generated. A limit must be specified for every selected configuration,
but the special value 0 can be used to indicate that no limit should
be observed for a given configuration. This version returns a vector<bool>
whose size equals the number of selected configurations. For each configuration,
the boolean is true if all possible valid timetables were generated
without exceeding the limit, and false otherwise.
Requires: GetState() == course_load_state::configs_selected
size_t CourseLoad::GetTimetableCount(size_t config_index) const;
Get the number of timetables generated for the selected activity configuration
identified by config_index
,
for this course load.
config_index
should
be the same as what you'd pass to GetSelectedConfig()
.
Requires: GetState() == course_load_state::timetables_generated
const __Timetable_page_rep__& CourseLoad::GetTimetable(size_t config_index, size_t timetable_index) const;
Get the generated timetable identified by timetable_index
for the selected activity configuration identified by config_index
, for this course load.
config_index
should
be the same as what you'd pass to GetSelectedConfig()
.
timetable_index
can
range from 0 to GetTimetableCount(config_index) - 1
.
The return value of this function is a reference to an object that lives inside the API implementation. Clients may make a copy of this object if desired, but this is unnecessary (and inefficient).
Requires: GetState() == course_load_state::timetables_generated
void CourseLoad::ClearTimetables();
Clear all generated timetables for this course load. Timetables are cleared for all selected activity configurations.
After this operation, the course load state will be course_load_state::configs_selected
.
Requires: GetState() == course_load_state::timetables_generated
vector<pair<string, string>> CourseLoad::GetConflicts(size_t config_index) const;
Get a list of pairs of strings, with each pair describing a pair of
conflicting meeting sections for the selected activity configuration
identified by config_index
for this course load.
Each string describes a meeting section by its activity/session code, section code, and list of timeslots.
This is useful in cases where there are no conflict-free timetables
for a given activity configuration (GetTimetableCount()
== 0), and you want to help the user
understand why.
Requires: GetState() == course_load_state::timetables_generated
timetable_sort_criteria CourseLoad::GetCurrentSortCriteria(size_t config_index) const;
Get the list of sort criteria used in the most recent call to SortTimetables()
for the specified selected configuration
in this course load. If SortTimetables()
has never been called for this configuration
in this session, an empty list is returned.
Requires: GetState() == course_load_state::timetables_generated
void CourseLoad::SortTimetables(size_t config_index, const timetable_sort_criteria& sort_criteria, ProgressIndicator progress_indicator);
Sort the generated timetables for the selected activity configuration
identified by config_index
for this course load, using the specified sort criteria.
sort_criteria
should
contain a subset of the sort criteria returned by GetTimetableSortCriteria()
The order of sort_criteria
is important: the timetables will be sorted first by the first criteria;
then, each set of timetables that are equivalent according to the first
criteria are sorted by the second criteria; and so on.
After sorting, GetTimetable(config_index, 0)
will be the best timetable according to the sort criteria, GetTimetable(config_index, 1)
will be the next best, and so on.
If there are many timetables (millions), the sorting process may take
a user-noticeably long time, so a progress indicator is provided to
allow the function to report its progress from time to time. See ProgressIndicator
for details.
Requires: GetState() == course_load_state::timetables_generated
size_t CourseLoad::GetMemoryUsagePerTimetable(size_t config_index) const;
Get the number of bytes of memory used by the internal representation
of each timetable for the selected activity configuration identified
by config_index
for
the this course.
This is provided to help the client choose appropriate timetable limits
when calling the version of GenerateTimetables()
that takes a timetable limit for
each selected configuration, in cases where the purpose of setting
the limit is to constrain the total memory consumed by the library
after generating timetables.
Requires: GetState() >= course_load_state::configs_selected