Skip to main content

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:

  1. creates a scheduling context
  2. validates the requested operation
  3. prepares a working solution
  4. constructs a feasible schedule
  5. optimizes the feasible solution
  6. validates the final solution
  7. 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

pytimetable-0.4.0.tar.gz (53.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytimetable-0.4.0-py3-none-any.whl (76.8 kB view details)

Uploaded Python 3

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

Hashes for pytimetable-0.4.0.tar.gz
Algorithm Hash digest
SHA256 e3bf2f34c034f6a30d04349a3a13afd45317a45ef174cdc2c28ba8e06ff3c166
MD5 6e295de03e4ed2bd4f20b4f36b27d8af
BLAKE2b-256 a59493630678c938d487b2f09a8a6ec2fbd66ad4a4389c7eae826b61d08af9e6

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytimetable-0.4.0.tar.gz:

Publisher: publish.yml on briansimpo/pytimetable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pytimetable-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 670f3fcfe89b402d6bba35a4174188f978a02baa4220263f302ed3fbac0fb77f
MD5 bd06a64ec226b65cfc45f0f8e1be1586
BLAKE2b-256 0574a4bacb2a857bed5db2f95c3a8fcdff2b168c6f533a714969f8666ea2e2f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytimetable-0.4.0-py3-none-any.whl:

Publisher: publish.yml on briansimpo/pytimetable

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page