schedulrv0.14.0

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

An ActivityRelationConstraint connects two activities with an ActivityRelation. It is a hard constraint: the solver never returns a solution that violates it. With .soft(SoftPenalty::new(category, level, weight)) it becomes a preference instead, and a broken relation costs weight.

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.
FixedOffset { offset } The second starts exactly offset units after the first starts (negative: before).
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, ScoreRule::prefer_within_bucket and ScoreRule::avoid_within_bucket construct them:

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. PreferWithinBucket and AvoidWithinBucket name a stretch of every period bucket instead, by offset from the bucket’s start — “not in the first period of a day”. KeepStart is used by repair to keep activities where they were.

Preferences that concern a person, a resource or a whole course rather than one activity are goals; the soft form of a day rule is described in Day and bucket rules. All of them are scored on the same three levels and reported in the same score_components.

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 or goals, solve() constructs a feasible plan and then optimises the score; without rules it returns the first feasible plan it constructs (see Solving). SolveStatistics::optimal tells you whether the search proved the result optimal.

Reading the score

Every Solution carries:

  • score: a Score with the fields hard, strong, medium, weak and soft;
  • score_components: one ScoreComponent per 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 how the default portfolio’s workers happen to be scheduled and can differ between runs. Add rules if you need to prefer one of them, or solve with SolveStrategy::ConstructThenOptimize, which repeats.

Edit this page on GitHub · Docs for v0.14.0