CASOON Open Source
Describe the plan, let the solver place it.
schedulr is a scheduling framework for Rust. You describe activities, resources, people and time windows as plain data; schedulr compiles them into a constraint model, solves it with unifier, and explains every conflict in the terms you used.
cargo add schedulrschedulr = "0.8"Input: 3 resources, 3 participants, 5 activities, 1 resource pool, 1 participant pool, 1 group
Calendar: 24-hour cycle, starts at 09 10 11 13 14 15, Tue 13:00-14:59 closed
Status: Feasible (optimal: true)
When Activity Resource Participants
Mon 09:00-10:00 a1 Kickoff Room Aurora Ana, Ben, Chris
Mon 10:00-12:00 a4 Solver deep dive Room Aurora Chris
Mon 13:00-16:00 a2 Rust fundamentals Room Aurora Ben
Tue 09:00-11:00 a3 Hands-on lab Lab 1 Ben
Tue 15:00-16:00 a5 Q&A Room Aurora Ana, Ben, Chris
Timeline (one column per hour, 09-16)
Mon Tue
09 10 11 12 13 14 15 09 10 11 12 13 14 15
Room Aurora a1 a4 a4 .. a2 a2 a2 .. .. .. .. .. .. a5
Room Birch .. .. .. .. .. .. .. .. .. .. .. .. .. ..
Lab 1 .. .. .. .. .. .. .. a3 a3 .. .. .. .. ..
Ana a1 .. .. .. .. .. .. .. .. .. .. .. .. a5
Ben a1 .. .. .. a2 a2 a2 a3 a3 .. .. .. .. a5
Chris a1 a4 a4 .. .. .. .. .. .. .. .. .. .. a5
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- dependency: the unifier constraint solver
- 1
- score levels, compared lexicographically
- 3
- solver searches for a single booking check
- 0
- unit and integration tests on master
- 54
What it does
Domain types, not solver variables
Resources, participants, activities, requirements and time windows are the whole public model. Solutions and conflicts come back in the same vocabulary, with your ids and names.
Let the solver choose rooms and people
Requirements can name an exact resource or ask for any resource of a type, with features, a minimum capacity or from a named pool. Participants come from pools, candidate lists or nested, overlapping groups.
Calendars and preferences
Periodic slot templates with absolute exceptions decide when activities may start. Score rules on Strong, Medium and Weak levels express preferences, and every solution lists how each rule scored.
Book, explain and repair
SchedulingState checks one create, move or cancel without a search. For batch plans there are explain, analyze, evaluate_move, suggest, compare and a repair that changes as little as possible.
Generated at build time
All examples →1. Two fixed lab sessions overlap in the same lab, with the same trainer Status: Infeasible Blocking NoOverlap [a1, a2]: Lab 1: Intervals [9, 11) and [10, 12) overlap Advisory NoOverlap [a1, a2]: Chris: Intervals [9, 11) and [10, 12) overlap 2. Three sessions at 09:00 need a room, but there are only two rooms Status: Infeasible Blocking AlternativeResourceCapacity [a1, a2, a3]: Room Aurora: capacity 1 is insufficient for the unresolved alternatives Blocking AlternativeResourceCapacity [a1, a2, a3]: Room Birch: capacity 1 is insufficient for the unresolved alternatives 3. Invalid input is rejected before any solving CompileError: resource r1 has zero capacity CompileError: activity a1 has an invalid time window CompileError: activity a2 requires unknown resource r9 CompileError: duplicate activity id a2
Captured from the example programs in the repository by scripts/regenerate-examples.sh. Every output on this site comes from schedulr itself.
examples/output/explain_conflicts.txtQuickstart
Three steps from a problem description to a plan or an explanation. The documentation covers the full DSL.
- Add
schedulrto your crate. - Describe resources, participants and activities, then
compilethe problem. - Call
solve()and read the assignments, orexplain()why no plan exists.
use schedulr::{
Activity, ActivityId, Resource, ResourceId, ResourceRequirement,
SchedulingProblem, SolveStatus, TimeWindow, compile,
};
let room = Resource::new(ResourceId(1), "Physics lab", 1);
let first = Activity::new(ActivityId(1), "first", TimeWindow::new(10, 20), 10)
.with_requirement(ResourceRequirement::new(room.id(), 1));
let second = Activity::new(ActivityId(2), "second", TimeWindow::new(15, 25), 10)
.with_requirement(ResourceRequirement::new(room.id(), 1));
let compiled = compile(&SchedulingProblem::new(vec![room], vec![], vec![first, second]))
.expect("problem compiles");
let result = compiled.solve();
if result.status != SolveStatus::Feasible {
for conflict in compiled.explain(&result) {
println!("{}: {}", conflict.constraint_name, conflict.message);
}
}