Relations and scores
Hard relations tie two activities together in time. Score rules express preferences on three levels, and every solution reports how each rule contributed.
Activity relations
Unreleased: available on the master branch, not in the 0.8.0 release on crates.io.
An ActivityRelationConstraint connects two activities with an ActivityRelation. It is a hard
constraint: the solver never returns a solution that violates it.
| Relation | Meaning |
|---|---|
SameStart |
Both activities start at the same time. |
Consecutive |
The second starts exactly when the first ends. |
Precedence { min_gap } |
The first ends at least min_gap units before the second starts. |
NoOverlap |
The two activities do not overlap in time. |
use schedulr::{ActivityId, ActivityRelation, ActivityRelationConstraint};
let problem = problem
.with_relation(ActivityRelationConstraint::new(
ActivityId(2),
ActivityId(3),
ActivityRelation::Consecutive,
))
.with_relation(ActivityRelationConstraint::new(
ActivityId(1),
ActivityId(2),
ActivityRelation::Precedence { min_gap: 1 },
));
NoOverlap is useful for activities that share no resource or participant, for example a
welcome session and an office hour that everyone should be able to attend. Activities that share
a resource with capacity 1 or a participant never overlap anyway.
Relations must reference known activities and cannot relate an activity to itself; both are compile errors.
Score rules
Preferences are ScoreRules. Each rule has a category (a name you choose), a level, the activity
it applies to, a positive weight, and a kind. ScoreRule::prefer_window is the public
constructor:
use schedulr::{ActivityId, ScoreLevel, ScoreRule, TimeWindow};
let problem = problem.with_score_rule(ScoreRule::prefer_window(
"kickoff on Monday at 09:00",
ScoreLevel::Strong,
ActivityId(1),
TimeWindow::new(9, 10),
1,
));
A PreferWindow rule costs -weight when the activity starts outside the preferred window and
0 when it starts inside. The second kind, KeepStart, is used by
repair to keep activities where they were.
Score levels
ScoreLevel has three tiers: Strong, Medium and Weak. They are compared
lexicographically: any improvement on a higher level beats every possible improvement on a lower
one, no matter the weights. Weights only compare rules on the same level.
When a problem has score rules, solve() runs branch and bound and optimises the score; without
rules it runs plain backtracking and returns the first feasible plan.
SolveStatistics::optimal tells you whether the search proved the result optimal.
Reading the score
Every Solution carries:
score: aScorewith the fieldshard,strong,medium,weakandsoft;score_components: oneScoreComponentper rule with its category, level, activity and value.
The components let an application show why a plan looks the way it does, and store the scores with each schedule version. From the workshop plan:
Score: hard 0, strong 0, medium 0, weak 0
Strong kickoff on Monday at 09:00 a1 0
Medium Q&A on Tuesday afternoon a5 0
Weak lab on Tuesday morning a3 0
When several plans reach the same best score, the one solve() returns depends on the search
order and can differ between runs. Add rules if you need to prefer one of them.