PyTimetable
PyTimetable is a domain-independent constraint-based scheduling framework for modeling, generating, validating, repairing, and optimizing timetables.
It provides reusable scheduling primitives for:
- activities
- participants
- facilitators
- venues
- locations
- time
- availability
- requirements
- constraints
- schedules and assignments
- solution validation
- incremental scheduling
- incremental repair
- optimization
PyTimetable is deliberately independent of application-specific concepts such as courses, semesters, curricula, classrooms, departments, or university programs.
Applications can construct PyTimetable problems directly or adapt their
own domain models into a PyTimetable Problem.
Application Domain
│
▼
ProblemAdapter
│
▼
Problem
│
▼
Solver
│
▼
Solution
│
▼
SolutionAdapter
│
▼
Application Result
The scheduling engine determines how activities are scheduled. The application determines what those activities represent.
Features
PyTimetable is designed around a complete scheduling lifecycle:
Problem
│
▼
Validate
│
▼
Construct Feasible Solution
│
▼
Optimize
│
▼
Validate Solution
│
▼
Solution
It also supports repairing an existing solution without rebuilding the entire timetable:
Existing Solution
│
▼
Copy
│
├───────────────┐
│ │
▼ ▼
Activities to Other activities
repair remain fixed
│ │
▼ │
Unassign │
│ │
└───────┬───────┘
▼
Scheduler
│
▼
Optimizer
│
▼
Repaired Solution
Core capabilities include:
- domain-independent scheduling
- explicit temporal modeling
- resource availability
- activity requirements
- hard constraint validation
- solution validation
- feasible-solution construction
- optimization of feasible schedules
- incremental scheduling
- incremental repair
- reproducible randomized optimization
- application/domain adapters
- spatial and travel modeling
- mutable and immutable domain collections
Installation
Install PyTimetable from PyPI:
pip install pytimetable
For local development:
git clone https://github.com/briansimpo/pytimetable.git
cd pytimetable
python -m venv venv
Windows
.\venv\Scripts\Activate.ps1
Linux / macOS
source venv/bin/activate
Install the project in editable mode:
pip install -e .
Quick Start
The basic workflow is:
Timeline
+
Resources
+
Activities
│
▼
Problem
│
▼
Solver
│
▼
Solution
1. Create a Timeline
A Timeline defines the schedulable temporal search space.
from datetime import time
from pytimetable.temporal.timeline import TimelineBuilder
timeline = TimelineBuilder(
days=5,
start_time=time(8, 0),
end_time=time(17, 0),
period_minutes=60,
).build()
This creates a five-day scheduling timeline with one-hour periods between 08:00 and 17:00.
2. Create Resources
PyTimetable schedules activities against resources such as facilitators and venues.
Create a location and venue type:
from pytimetable.models.location import Location
from pytimetable.models.venuetype import VenueType
main_building = Location(
name="Main Building",
)
lecture_room = VenueType(
name="Lecture Room",
)
Create a venue:
from pytimetable.models.venue import Venue, Venues
from pytimetable.temporal.availability import Availability
room_a = Venue(
name="Room A",
capacity=100,
location=main_building,
venue_type=lecture_room,
availability=Availability(
timeline.timeslots,
),
)
venues = Venues([
room_a,
])
Create an eligible facilitator:
from pytimetable.models.facilitator import (
Facilitator,
Facilitators,
)
lecturer = Facilitator(
name="Dr Ada Lovelace",
availability=Availability(
timeline.timeslots,
),
)
facilitators = Facilitators([
lecturer,
])
3. Create a Participant
A Participant represents the person or group that must attend an
activity.
from pytimetable.models.participant import Participant
students = Participant(
name="Computer Science Y1",
size=40,
availability=Availability(
timeline.timeslots,
),
)
Participants can represent an individual, group, class, cohort, or any other schedulable attendee.
4. Create an Event
An Event represents a broader event to which one or more activities
belong.
It can also define the facilitators eligible to conduct those activities.
from pytimetable.models.event import Event
programming = Event(
name="Programming I",
facilitators=facilitators,
)
An event can therefore group related activities:
Programming I
├── Lecture
├── Lab
└── Tutorial
Each schedulable component remains an independent Activity.
5. Create an Activity
An Activity is the unit that the solver places into the timetable.
The following activity represents a two-period lecture requiring one facilitator and a lecture room with sufficient capacity.
from pytimetable.models.activity import (
Activity,
Activities,
)
from pytimetable.requirements.facilitator import (
FacilitatorRequirement,
)
from pytimetable.requirements.timeslot import (
TimeslotRequirement,
)
from pytimetable.requirements.venue import (
VenueRequirement,
)
from pytimetable.temporal.duration import Duration
lecture = Activity(
name="Lecture",
event=programming,
participant=students,
duration=Duration(
periods=2,
),
facilitator_requirement=(
FacilitatorRequirement(
minimum=1,
maximum=1,
)
),
venue_requirement=(
VenueRequirement(
capacity=students.size,
venue_type=lecture_room,
)
),
timeslot_requirement=(
TimeslotRequirement()
),
)
activities = Activities([
lecture,
])
The scheduling engine does not need to know what a university lecture means.
The activity declares what it needs:
Duration → 2 periods
Participant → Computer Science Y1
Facilitators → 1 required
Venue capacity → 40
Venue type → Lecture Room
The scheduler is responsible for finding an assignment satisfying those requirements.
6. Build the Problem
Combine the activities, venues, timeline, and other scheduling
information into a Problem.
from pytimetable.problem import Problem
from pytimetable.spatial.travel import TravelTimes
problem = Problem(
activities=activities,
venues=venues,
timeline=timeline,
travel_times=TravelTimes(),
)
The Problem is the domain-independent representation consumed by the
scheduling engine.
7. Solve
Pass the problem to Solver:
from pytimetable.solver import Solver
solution = Solver(
seed=42,
).solve(
problem,
)
The solver:
- creates a scheduling context
- validates the requested operation
- prepares a working solution
- constructs a feasible schedule
- optimizes the feasible solution
- validates the final solution
- returns the resulting
Solution
A seed can be supplied to make randomized solver behaviour reproducible during development and testing.
Solver
Solver is the high-level orchestration API.
solver = Solver(
seed=42,
)
solution = solver.solve(
problem,
)
The solver supports three closely related workflows.
Full Solve
A full solve starts with an empty solution:
Problem
│
▼
Empty Solution
│
▼
Scheduler
│
▼
Feasible Solution
│
▼
Optimizer
│
▼
Validated Solution
Use this when there is no existing timetable to preserve.
Incremental Scheduling
Incremental scheduling extends an existing timetable without rebuilding or moving schedules that have already been solved.
Pass the existing Solution to Solver.solve():
existing = Solver(
seed=42,
).solve(
first_problem,
)
solution = Solver(
seed=42,
).solve(
second_problem,
existing_solution=existing,
)
The existing solution is copied and becomes the baseline for the new solve.
The new activities are then scheduled around the existing assignments.
Existing Solution
│
│ preserved
▼
┌─────────────┐
│ Solver │ ◄──── New Problem
└──────┬──────┘
│
▼
Complete Solution
┌───────────────────────┐
│ Existing schedules │ fixed
│ New activities │ scheduled + optimized
└───────────────────────┘
Existing participant, facilitator, venue, availability, and temporal constraints continue to apply because the existing schedules remain part of the working solution.
The original solution is not modified.
When to use incremental scheduling
Incremental scheduling is useful for:
- adding another programme to an existing timetable
- adding a new department after the main timetable has been solved
- scheduling a new group around an already published timetable
- extending a timetable when new activities become available
Incremental scheduling uses the same Solver, Scheduler, constraints,
validators, and optimization pipeline as a normal solve.
There is no separate incremental solver API.
Incremental Repair
Incremental repair allows selected activities in an existing timetable to be removed and scheduled again without rebuilding the entire timetable.
This is useful when only part of a timetable has become invalid, undesirable, or in need of revision.
Use reschedule_activities to identify the activities that should be
repaired:
solution = Solver(
seed=42,
).solve(
problem,
existing_solution=existing,
reschedule_activities=Activities([
lecture,
tutorial,
]),
)
The repair process is:
Existing Solution
│
▼
Copy Solution
│
├────────────────────────┐
│ │
▼ ▼
Activities to repair Other activities
│ │
▼ ▼
Unassigned Fixed
│
└───────────┬────────────┘
│
▼
Scheduler
│
▼
Feasible repaired solution
│
▼
Optimizer
│
▼
Repaired Solution
Only activities supplied through reschedule_activities are unassigned.
All other activities remain assigned and are treated as fixed during optimization.
Example
Suppose an existing timetable contains:
Lecture A Monday
Lecture B Tuesday
Lecture C Wednesday
Tutorial A Thursday
Tutorial B Friday
If only Lecture B must be repaired:
solution = Solver(
seed=42,
).solve(
problem,
existing_solution=existing,
reschedule_activities=Activities([
lecture_b,
]),
)
The solver effectively works with:
Lecture A fixed
Lecture B removed → rescheduled
Lecture C fixed
Tutorial A fixed
Tutorial B fixed
The scheduler finds a new assignment for Lecture B while respecting
the existing assignments.
The optimizer then improves the resulting solution without modifying the fixed activities.
Repair validation
Repair requests are validated before the working solution is modified.
reschedule_activities cannot be supplied without an existing solution:
Solver().solve(
problem,
reschedule_activities=Activities([
lecture,
]),
)
This is invalid because there is no existing solution from which the activity can be repaired.
When an existing solution is supplied, RepairValidator validates the
repair request against the problem and existing solution.
Typical repair scenarios
Incremental repair is useful for:
- a facilitator becoming unavailable
- a venue becoming unavailable
- a venue being reassigned
- a participant group changing availability
- an activity requiring a different venue type
- correcting an undesirable assignment
- moving a small set of conflicting activities
- repairing part of a published timetable after a timetable change
Incremental repair vs full rescheduling
Full rescheduling:
Problem
│
▼
Empty Solution
│
▼
Scheduler
│
▼
Optimizer
│
▼
Complete Solution
Incremental repair:
Existing Solution
│
▼
Copy
│
├── Activities being repaired
│ │
│ ▼
│ Unassigned
│
└── Other activities
│
▼
Fixed
│
▼
Scheduler
│
▼
Optimizer
│
▼
Repaired Solution
Incremental repair therefore localizes the change instead of treating the entire timetable as new.
Incremental Scheduling with Application Adapters
Applications using adapters follow the same pattern.
Adapt each scheduling batch into a Problem, solve the first batch, and
pass the resulting Solution into the next solve.
problem_adapter = ProblemAdapter(
timeline=timeline,
)
first_problem = problem_adapter.adapt(
first_domain_source,
)
second_problem = problem_adapter.adapt(
second_domain_source,
)
solver = Solver(
seed=42,
)
existing = solver.solve(
first_problem,
)
solution = solver.solve(
second_problem,
existing_solution=existing,
)
The final solution contains both the existing schedules and the newly scheduled activities.
Incremental Repair with Application Adapters
The same adapter architecture can be used for repair.
problem_adapter = ProblemAdapter(
timeline=timeline,
)
problem = problem_adapter.adapt(
domain_source,
)
existing = solver.solve(
problem,
)
activities_to_repair = Activities([
activity_to_repair,
])
solution = solver.solve(
problem,
existing_solution=existing,
reschedule_activities=activities_to_repair,
)
The application remains responsible for deciding which domain objects need repair.
The scheduling kernel remains responsible for determining how those activities can be repaired.
Core Concepts
PyTimetable represents scheduling problems using a small set of domain-independent concepts.
Activity
An Activity represents something that must be scheduled.
Examples include:
- a lecture
- a laboratory session
- a tutorial
- an examination
- a meeting
- a training session
An activity describes its duration, participant, and scheduling requirements.
The solver determines where and when the activity should be scheduled.
Activities
Activities is the domain collection used for groups of activities.
For example:
activities = Activities([
lecture,
laboratory,
tutorial,
])
Collections provide common operations for querying and manipulating groups of scheduling objects.
They are used throughout the scheduling model instead of exposing implementation-specific collection types at the domain boundary.
Event
An Event groups related scheduling information and identifies
facilitators eligible to conduct its activities.
For example:
Programming I
├── Lecture
├── Lab
└── Tutorial
Each schedulable component remains an Activity.
Participant
A Participant represents the person or group that must attend an
activity.
For a university timetable, a participant might represent:
Computer Science
Year 1
Semester 1
Participant availability and conflicts are considered when constructing the timetable.
Facilitator
A Facilitator represents a resource responsible for conducting an
activity.
Examples include:
- lecturers
- teachers
- instructors
- supervisors
An event can have multiple eligible facilitators. The scheduler selects an appropriate facilitator while respecting requirements, availability, and conflicts.
Venue
A Venue represents a physical location where an activity can take
place.
A venue can describe:
- capacity
- venue type
- location
- availability
For example:
Lecture Room A
Capacity: 120
Type: Lecture Room
Location: Main Building
Venue Type
A VenueType describes the purpose or category of a venue.
Examples include:
Lecture Room
Computer Lab
Tutorial Room
Chemistry Lab
Workshop
Studio
An activity can require a particular venue type.
This allows the scheduler to prevent incompatible assignments such as placing a computer laboratory activity in an ordinary lecture room.
Time Model
PyTimetable explicitly models scheduling time through days, periods, timeslots, durations, and timelines.
These concepts are intentionally separate.
Day
│
└── Period
│
▼
Timeslot
Activity
│
└── Duration
A Timeslot represents a position in the timetable.
A Duration represents how much scheduling time an activity requires.
A Timeline defines the complete temporal search space.
The temporal model does not treat a duration as a timeslot, and a timeslot is not embedded into a duration.
Day
A Day represents a schedulable day.
from pytimetable.temporal.day import Day
monday = Day(1)
print(monday)
# Monday
Period
A Period represents a schedulable period within a day.
It contains an internal period number and its real-world time range.
from datetime import time
from pytimetable.temporal.period import Period
period = Period(
number=1,
start_time=time(7, 30),
end_time=time(8, 20),
)
print(period)
# 07:30-08:20
The period number provides a convenient internal representation while the start and end times represent the actual time range.
Timeslot
A Timeslot combines a Day and a Period.
Timeslot
├── Day
└── Period
For example:
Monday
07:30-08:20
represents one schedulable position in the timetable.
Duration
A Duration describes how much scheduling time an activity occupies.
from pytimetable.temporal.duration import Duration
duration = Duration(
periods=2,
)
This means the activity requires two consecutive schedulable periods.
A duration is a temporal length, not a starting position.
Timeline
A Timeline defines the complete temporal search space for a scheduling
problem.
from datetime import time
from pytimetable.temporal.timeline import TimelineBuilder
timeline = TimelineBuilder(
days=5,
start_time=time(7, 30),
end_time=time(17, 0),
period_minutes=50,
).build()
The timeline contains the timeslots in which activities may be scheduled.
Applications can therefore define their scheduling calendar independently of the solver.
Availability
Resources can specify when they are available.
from pytimetable.temporal.availability import Availability
availability = Availability(
timeline.timeslots,
)
Availability can be associated with:
- participants
- facilitators
- venues
The scheduler uses availability when determining valid assignments.
Requirements
Activities describe what they need through explicit requirements.
This keeps scheduling rules separate from application-specific concepts.
Facilitator Requirement
An activity can specify how many facilitators it requires.
FacilitatorRequirement(
minimum=1,
maximum=1,
)
The scheduler selects from eligible facilitators associated with the event.
Venue Requirement
An activity can specify venue requirements such as capacity and venue type.
VenueRequirement(
capacity=60,
venue_type=computer_lab,
)
A compatible venue must satisfy the specified requirements.
Timeslot Requirement
An activity can describe temporal requirements:
TimeslotRequirement()
Together with the timeline and availability information, timeslot requirements determine when an activity can be placed.
Assignments and Schedules
A scheduling solution is composed of assignments represented by schedules.
Conceptually:
Activity
│
▼
Assignment
│
├── Timeslot
├── Venue
└── Facilitator(s)
A schedule records the placement of an activity in the timetable.
The Solution contains the schedules produced by the scheduling
process.
This separation allows the activity itself to describe what must be scheduled, while the schedule describes where and when it was scheduled.
Problem
A Problem represents the complete scheduling problem presented to the
solver.
It brings together concepts such as:
Problem
├── Activities
├── Venues
├── Timeline
├── Travel Times
└── Scheduling Context
The application can construct the problem directly or create it through
a ProblemAdapter.
Solution
A Solution represents a candidate timetable.
It contains:
- schedules
- activity assignments
- timeline information
- the state required for constraint evaluation and optimization
Solutions are mutable during construction and optimization.
This is important for incremental scheduling and repair because the solver can work from a copy of an existing solution without modifying the original.
Problem Context
ProblemContext groups the runtime services required while solving a
problem.
Conceptually:
ProblemContext
├── Problem
├── Evaluator
└── Scorer
The context is created once by the solver and passed through the scheduling and optimization pipeline.
This keeps the solver components working against the same problem state and evaluation services.
Scheduler
The Scheduler is responsible for constructing a feasible solution.
Its role is primarily:
Prepared Solution
│
▼
Scheduler
│
▼
Feasible Solution
The scheduler does not need to understand application-specific concepts.
It works with activities, requirements, resources, availability, timelines, and constraints.
If a feasible solution cannot be constructed, the solver raises an error rather than returning an invalid schedule.
Optimizer
The Optimizer improves an already feasible solution.
Its role is:
Feasible Solution
│
▼
Optimizer
│
▼
Improved Solution
The optimizer operates on the scheduling context and can explore alternative assignments.
During incremental repair, activities that are not being repaired are treated as fixed.
Conceptually:
optimizer.optimize(
solution=solution,
fixed_activities=fixed_activities,
)
The optimization layer therefore distinguishes between:
- activities that may be changed
- activities that must remain fixed
This is what makes optimization compatible with incremental repair.
Feasibility
A timetable is feasible when all required hard scheduling constraints are satisfied.
Typical feasibility requirements include:
- no participant is assigned to overlapping activities
- no facilitator is assigned to overlapping activities
- no venue is double-booked
- assigned venues have sufficient capacity
- assigned venues satisfy venue-type requirements
- resources are available during their assignments
- activities fit within the timeline
- multi-period activities occupy valid consecutive periods
For example:
Programming Lab
│
▼
VenueRequirement
│
▼
Computer Lab
is feasible if the assigned venue satisfies the activity's type, capacity, and availability requirements.
A feasible timetable is valid.
It is not necessarily a high-quality timetable.
Solution Validation
Feasibility is checked as part of the scheduling lifecycle.
After optimization, the final solution is validated again:
Scheduler
│
▼
Feasible Solution
│
▼
Optimizer
│
▼
SolutionValidator
│
▼
Validated Solution
This ensures that optimization does not return a solution that violates the required constraints.
Timetable Quality
Once feasibility has been achieved, the schedule can be evaluated and optimized for quality.
Typical quality considerations include:
- minimizing participant gaps
- minimizing facilitator gaps
- limiting excessive consecutive teaching periods
- reducing venue changes
- reducing travel between locations
- distributing activities across the week
- avoiding undesirable times
- balancing resource utilization
These considerations may compete with each other.
For example:
09:00-10:00 Activity A
10:00-11:00 Activity B
11:00-12:00 Activity C
12:00-13:00 Activity D
has excellent compactness but potentially undesirable consecutive load.
Scheduling quality is therefore an optimization problem rather than a single universal rule.
Feasibility vs Optimization
PyTimetable separates two concerns:
Scheduling
│
┌──────────┴──────────┐
│ │
Feasibility Quality
│ │
Hard constraints Optimization
│ │
Must be valid Should be better
Hard constraints determine whether a schedule can be accepted.
Optimization distinguishes between feasible solutions and searches for better ones.
Correctness therefore comes before quality.
Spatial and Travel Modeling
PyTimetable can represent spatial relationships between locations and travel times between them.
This allows scheduling quality and constraints to account for movement between venues.
For example:
Venue A
│
│ travel time
▼
Venue B
Travel information can be supplied when constructing a problem:
from pytimetable.spatial.travel import TravelTimes
problem = Problem(
activities=activities,
venues=venues,
timeline=timeline,
travel_times=TravelTimes(),
)
Applications can therefore model scheduling environments where changing locations has a temporal or quality cost.
Building Application Integrations
The Quick Start constructs PyTimetable objects directly.
That approach is useful for:
- learning the API
- tests
- scripts
- small scheduling applications
- domains already closely matching the PyTimetable model
Larger applications should normally keep their own domain model and adapt it to PyTimetable.
A university application might contain:
Course
CourseComponent
Program
Student
Lecturer
Building
Room
CourseRegistration
These are application concepts and do not need to be replaced with PyTimetable classes.
Instead:
University Domain
│
▼
ProblemAdapter
│
▼
PyTimetable Problem
│
▼
Solver
│
▼
PyTimetable Solution
│
▼
SolutionAdapter
│
▼
University Timetable
This allows applications to use Django models, SQLAlchemy models, dataclasses, API objects, or any other data representation without coupling PyTimetable to those representations.
ProblemAdapter
A ProblemAdapter translates application-domain objects into a
PyTimetable Problem.
The adapter owns the mapping between application concepts and scheduling concepts.
For example:
Application PyTimetable
CourseComponent ─────────→ Activity
Student Group ─────────→ Participant
Lecturer ─────────→ Facilitator
Room ─────────→ Venue
Room Type ─────────→ VenueType
The adapter should not move application business logic into the scheduling kernel.
Instead, it translates application requirements into generic scheduling requirements.
SolutionAdapter
A SolutionAdapter translates a PyTimetable Solution back into the
application's domain representation.
Conceptually:
PyTimetable Solution
│
▼
SolutionAdapter
│
▼
Application Result
For example, an application may transform:
Activity
Timeslot
Venue
Facilitator
into:
Course
Day
Period
Room
Lecturer
The application therefore remains responsible for its own persistence and presentation model.
Domain Context
DomainContext can be used by adapters to maintain relationships
between application-domain objects and their PyTimetable equivalents.
Conceptually:
Application PyTimetable
Lecturer ←──────────→ Facilitator
Room ←──────────→ Venue
CourseComponent←──────────→ Activity
This becomes particularly useful after solving the problem.
A SolutionAdapter can use the same context to recover the application
objects associated with scheduling assignments.
For example:
solution_adapter = SolutionAdapter(
context=problem_adapter.context,
)
result = solution_adapter.adapt(
solution,
)
Example: University Timetable
Consider a university course with three teaching components:
Programming I
├── Lecture
│ ├── Duration: 2 periods
│ └── Venue type: Lecture Room
│
├── Lab
│ ├── Duration: 2 periods
│ └── Venue type: Computer Lab
│
└── Tutorial
├── Duration: 1 period
└── Venue type: Tutorial Room
The university application can model these requirements explicitly:
programming = Course(
id=uuid4(),
name="Programming I",
components=(
CourseComponent(
name="Lecture",
periods=2,
venue_type=lecture_type,
),
CourseComponent(
name="Lab",
periods=2,
venue_type=lab_type,
),
CourseComponent(
name="Tutorial",
periods=1,
venue_type=tutorial_type,
),
),
)
The application's ProblemAdapter converts each course component into
an Activity.
The component's venue requirement becomes a PyTimetable
VenueRequirement.
CourseComponent
│
├── periods
└── venue_type
│
▼
ProblemAdapter
│
▼
Activity
│
├── Duration
└── VenueRequirement
│
▼
Solver
│
▼
Compatible Venue
The resulting timetable might contain:
Day Time Participant Course Component Venue
------------------------------------------------------------------------------------------------
Monday 12:30-14:20 Computer Science Y1 S1 Programming I Lab Computer Lab
Wednesday 08:30-09:20 Computer Science Y1 S1 Programming I Tutorial Tutorial Room
Friday 11:30-13:20 Computer Science Y1 S1 Programming I Lecture Lecture Room B
PyTimetable itself does not contain rules such as:
if activity.name == "Lab":
assign_computer_lab()
Instead, the activity declares a requirement for a particular venue type.
The generic scheduling engine is responsible only for satisfying that requirement.
Architecture
At a high level:
┌─────────────────────────────────────┐
│ Application Domain │
│ │
│ Courses, Students, Rooms, etc. │
└──────────────────┬──────────────────┘
│
│ ProblemAdapter
▼
┌─────────────────────────────────────┐
│ PyTimetable Problem │
│ │
│ Activities │
│ Participants │
│ Facilitators │
│ Venues │
│ Timeline │
│ Requirements │
│ Constraints │
└──────────────────┬──────────────────┘
│
│ Solver
▼
┌─────────────────────────────────────┐
│ Solution │
│ │
│ Activity assignments │
│ Timeslots │
│ Venues │
│ Facilitators │
└──────────────────┬──────────────────┘
│
│ SolutionAdapter
▼
┌─────────────────────────────────────┐
│ Application Result │
└─────────────────────────────────────┘
The application owns its business domain.
PyTimetable owns the scheduling problem.
The adapter boundary connects the two.
Design Principles
Domain Independent
PyTimetable should not know what a course, semester, curriculum, classroom, or university program is.
Those concepts belong to the application domain.
PyTimetable operates on generic scheduling concepts such as:
Activity
Participant
Facilitator
Venue
Timeline
Requirement
Constraint
Schedule
Solution
Requirements Over Special Cases
Scheduling behaviour should be expressed through requirements and constraints rather than hard-coded domain rules.
Avoid:
if activity.name == "Lab":
use_computer_lab()
Prefer:
Activity
│
└── VenueRequirement
│
└── VenueType: Computer Lab
The engine then satisfies the generic requirement.
Explicit Domain Boundaries
Application models should not need to inherit from or depend directly on PyTimetable scheduling models.
Adapters provide the translation boundary.
This keeps both sides independently evolvable.
Explicit Time Model
Days, periods, timeslots, durations, and timelines are explicit scheduling concepts rather than implicit integer conventions.
A period contains its real-world time range:
Period 1 → 07:30-08:20
Period 2 → 08:30-09:20
Period 3 → 09:30-10:20
The scheduling engine can work with the temporal model while applications can display actual times.
Feasibility Before Quality
Correctness comes first.
Hard constraints establish feasibility.
Optimization then improves the quality of feasible solutions.
Incremental Change
Existing solutions should not need to be rebuilt when only part of a timetable changes.
Incremental scheduling preserves an existing solution while adding activities.
Incremental repair preserves an existing solution while explicitly rescheduling selected activities.
Both use the same scheduling pipeline.
Reproducibility
Randomized scheduling behaviour can be seeded:
solution = Solver(
seed=42,
).solve(
problem,
)
This makes development, testing, debugging, and performance comparisons reproducible.
Composability
Scheduling concepts should remain small and composable.
Complex scheduling behaviour should emerge from combinations of:
Activities
Resources
Requirements
Constraints
Algorithms
rather than increasingly specialized domain-specific classes.
Development
Clone the repository:
git clone https://github.com/briansimpo/pytimetable.git
cd pytimetable
Create a virtual environment:
python -m venv venv
Activate it.
Windows:
.\venv\Scripts\Activate.ps1
Linux / macOS:
source venv/bin/activate
Install PyTimetable in editable mode:
pip install -e .
Run the university timetable example:
python -m examples.timetable
Example Output
A generated university timetable may look like:
Day Time Participant Course Component Venue Lecturer
Monday 12:30-14:20 Computer Science Y1 S1 Programming I Lab Computer Lab Dr Alan Turing
Tuesday 13:30-15:20 Information Technology Y1 S1 Programming I Lab Computer Lab Dr Grace Hopper
Wednesday 07:30-08:20 Computer Science Y1 S1 Mathematics I Tutorial Tutorial Room Dr Ada Lovelace
Wednesday 08:30-09:20 Computer Science Y1 S1 Programming I Tutorial Tutorial Room Dr Alan Turing
Thursday 12:30-14:20 Information Technology Y1 S1 Academic Writing Lecture Lecture Room B Dr Ada Lovelace
Thursday 14:30-15:20 Information Technology Y1 S1 Academic Writing Tutorial Tutorial Room Dr Ada Lovelace
Thursday 15:30-16:20 Information Technology Y1 S1 Programming I Tutorial Tutorial Room Dr Grace Hopper
Friday 07:30-09:20 Information Technology Y1 S1 Programming I Lecture Lecture Room A Dr Grace Hopper
Friday 09:30-11:20 Computer Science Y1 S1 Mathematics I Lecture Lecture Room B Dr Ada Lovelace
Friday 11:30-13:20 Computer Science Y1 S1 Programming I Lecture Lecture Room B Dr Alan Turing
In this example:
- participant conflicts are avoided
- facilitator conflicts are avoided
- venues are not double-booked
- laboratories use compatible laboratory venues
- tutorials use compatible tutorial venues
- lectures use compatible lecture rooms
- multi-period activities occupy consecutive scheduling periods
- eligible facilitators are selected by the scheduler
Optimization can then improve qualities such as gaps, consecutive load, room changes, travel, and weekly distribution.
Project Status
PyTimetable is under active development.
Current areas include:
- generic scheduling primitives
- timetable feasibility
- constraint evaluation
- solution validation
- timetable quality evaluation
- local-search optimization
- multi-objective optimization
- resource availability
- spatial and travel constraints
- reusable domain adapters
- incremental scheduling
- incremental repair
The public API may evolve while the architecture is being refined.
Contributing
Contributions, bug reports, design discussions, and feature proposals are welcome.
When contributing, preserve PyTimetable's central design principle:
Application domains describe what must be scheduled. PyTimetable determines how to schedule it.
Domain-specific behaviour should generally remain outside the scheduling kernel unless it can be expressed as a reusable scheduling concept.
When extending the framework, prefer:
- small composable domain objects
- explicit types
- explicit temporal concepts
- requirements over special cases
- reusable constraints
- immutable value objects where appropriate
- validation at clear boundaries
- incremental operations where rebuilding is unnecessary
License
PyTimetable is licensed under the MIT License.
See LICENSE for the full license text.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pytimetable-0.4.0.tar.gz.
File metadata
- Download URL: pytimetable-0.4.0.tar.gz
- Upload date:
- Size: 53.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e3bf2f34c034f6a30d04349a3a13afd45317a45ef174cdc2c28ba8e06ff3c166
|
|
| MD5 |
6e295de03e4ed2bd4f20b4f36b27d8af
|
|
| BLAKE2b-256 |
a59493630678c938d487b2f09a8a6ec2fbd66ad4a4389c7eae826b61d08af9e6
|
Provenance
The following attestation bundles were made for pytimetable-0.4.0.tar.gz:
Publisher:
publish.yml on briansimpo/pytimetable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytimetable-0.4.0.tar.gz -
Subject digest:
e3bf2f34c034f6a30d04349a3a13afd45317a45ef174cdc2c28ba8e06ff3c166 - Sigstore transparency entry: 2475271296
- Sigstore integration time:
-
Permalink:
briansimpo/pytimetable@fa03dade18602e464d7ae3328c48ba8beef1cfb8 -
Branch / Tag:
refs/tags/0.4.0 - Owner: https://github.com/briansimpo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fa03dade18602e464d7ae3328c48ba8beef1cfb8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pytimetable-0.4.0-py3-none-any.whl.
File metadata
- Download URL: pytimetable-0.4.0-py3-none-any.whl
- Upload date:
- Size: 76.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
670f3fcfe89b402d6bba35a4174188f978a02baa4220263f302ed3fbac0fb77f
|
|
| MD5 |
bd06a64ec226b65cfc45f0f8e1be1586
|
|
| BLAKE2b-256 |
0574a4bacb2a857bed5db2f95c3a8fcdff2b168c6f533a714969f8666ea2e2f0
|
Provenance
The following attestation bundles were made for pytimetable-0.4.0-py3-none-any.whl:
Publisher:
publish.yml on briansimpo/pytimetable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytimetable-0.4.0-py3-none-any.whl -
Subject digest:
670f3fcfe89b402d6bba35a4174188f978a02baa4220263f302ed3fbac0fb77f - Sigstore transparency entry: 2475271318
- Sigstore integration time:
-
Permalink:
briansimpo/pytimetable@fa03dade18602e464d7ae3328c48ba8beef1cfb8 -
Branch / Tag:
refs/tags/0.4.0 - Owner: https://github.com/briansimpo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fa03dade18602e464d7ae3328c48ba8beef1cfb8 -
Trigger Event:
release
-
Statement type: