Metadata-Version: 2.5
Name: pytimetable
Version: 0.4.0
Summary: A domain-independent constraint-based scheduling framework for modeling, generating, and optimizing timetable.
Project-URL: Homepage, https://github.com/briansimpo/pytimetable
Project-URL: Documentation, https://github.com/briansimpo/pytimetable#readme
Project-URL: Source, https://github.com/briansimpo/pytimetable
Project-URL: Issues, https://github.com/briansimpo/pytimetable/issues
Author-email: Brian Simpokolwe <briansimpokolwe@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: constraint-satisfaction,optimization,scheduling,timetable,timetabling
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Description-Content-Type: text/markdown

# 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`.

``` text
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:

``` text
Problem
   │
   ▼
Validate
   │
   ▼
Construct Feasible Solution
   │
   ▼
Optimize
   │
   ▼
Validate Solution
   │
   ▼
Solution
```

It also supports repairing an existing solution without rebuilding the
entire timetable:

``` text
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:

``` bash
pip install pytimetable
```

For local development:

``` bash
git clone https://github.com/briansimpo/pytimetable.git
cd pytimetable

python -m venv venv
```

## Windows

``` powershell
.\venv\Scripts\Activate.ps1
```

## Linux / macOS

``` bash
source venv/bin/activate
```

Install the project in editable mode:

``` bash
pip install -e .
```

------------------------------------------------------------------------

# Quick Start

The basic workflow is:

``` text
Timeline
   +
Resources
   +
Activities
   │
   ▼
Problem
   │
   ▼
Solver
   │
   ▼
Solution
```

## 1. Create a Timeline

A `Timeline` defines the schedulable temporal search space.

``` python
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:

``` python
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:

``` python
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:

``` python
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.

``` python
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.

``` python
from pytimetable.models.event import Event


programming = Event(
    name="Programming I",
    facilitators=facilitators,
)
```

An event can therefore group related activities:

``` text
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.

``` python
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:

``` text
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`.

``` python
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`:

``` python
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.

``` python
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:

``` text
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()`:

``` python
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.

``` text
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:

``` python
solution = Solver(
    seed=42,
).solve(
    problem,
    existing_solution=existing,
    reschedule_activities=Activities([
        lecture,
        tutorial,
    ]),
)
```

The repair process is:

``` text
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:

``` text
Lecture A     Monday
Lecture B     Tuesday
Lecture C     Wednesday
Tutorial A    Thursday
Tutorial B    Friday
```

If only `Lecture B` must be repaired:

``` python
solution = Solver(
    seed=42,
).solve(
    problem,
    existing_solution=existing,
    reschedule_activities=Activities([
        lecture_b,
    ]),
)
```

The solver effectively works with:

``` text
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:

``` python
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:

``` text
Problem
   │
   ▼
Empty Solution
   │
   ▼
Scheduler
   │
   ▼
Optimizer
   │
   ▼
Complete Solution
```

Incremental repair:

``` text
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.

``` python
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.

``` python
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:

``` python
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:

``` text
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:

``` text
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:

``` text
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:

``` text
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.

``` text
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.

``` python
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.

``` python
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`.

``` text
Timeslot
├── Day
└── Period
```

For example:

``` text
Monday
07:30-08:20
```

represents one schedulable position in the timetable.

------------------------------------------------------------------------

## Duration

A `Duration` describes how much scheduling time an activity occupies.

``` python
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.

``` python
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.

``` python
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.

``` python
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.

``` python
VenueRequirement(
    capacity=60,
    venue_type=computer_lab,
)
```

A compatible venue must satisfy the specified requirements.

------------------------------------------------------------------------

## Timeslot Requirement

An activity can describe temporal requirements:

``` python
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:

``` text
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:

``` text
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:

``` text
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:

``` text
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:

``` text
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:

``` python
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:

``` text
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:

``` text
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:

``` text
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:

``` text
                 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:

``` text
Venue A
   │
   │ travel time
   ▼
Venue B
```

Travel information can be supplied when constructing a problem:

``` python
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:

``` text
Course
CourseComponent
Program
Student
Lecturer
Building
Room
CourseRegistration
```

These are application concepts and do not need to be replaced with
PyTimetable classes.

Instead:

``` text
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:

``` text
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:

``` text
PyTimetable Solution
       │
       ▼
 SolutionAdapter
       │
       ▼
Application Result
```

For example, an application may transform:

``` text
Activity
Timeslot
Venue
Facilitator
```

into:

``` text
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:

``` text
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:

``` python
solution_adapter = SolutionAdapter(
    context=problem_adapter.context,
)

result = solution_adapter.adapt(
    solution,
)
```

------------------------------------------------------------------------

# Example: University Timetable

Consider a university course with three teaching components:

``` text
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:

``` python
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`.

``` text
CourseComponent
       │
       ├── periods
       └── venue_type
              │
              ▼
       ProblemAdapter
              │
              ▼
           Activity
              │
              ├── Duration
              └── VenueRequirement
                      │
                      ▼
                    Solver
                      │
                      ▼
               Compatible Venue
```

The resulting timetable might contain:

``` text
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:

``` python
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:

``` text
┌─────────────────────────────────────┐
│          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:

``` text
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:

``` python
if activity.name == "Lab":
    use_computer_lab()
```

Prefer:

``` text
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:

``` text
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:

``` python
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:

``` text
Activities
Resources
Requirements
Constraints
Algorithms
```

rather than increasingly specialized domain-specific classes.

------------------------------------------------------------------------

# Development

Clone the repository:

``` bash
git clone https://github.com/briansimpo/pytimetable.git
cd pytimetable
```

Create a virtual environment:

``` bash
python -m venv venv
```

Activate it.

Windows:

``` powershell
.\venv\Scripts\Activate.ps1
```

Linux / macOS:

``` bash
source venv/bin/activate
```

Install PyTimetable in editable mode:

``` bash
pip install -e .
```

Run the university timetable example:

``` bash
python -m examples.timetable
```

------------------------------------------------------------------------

# Example Output

A generated university timetable may look like:

``` text
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](LICENSE) for the full license text.
