Skip to content
JAOT

Education Timetabling

Optimize class scheduling, resource allocation, and student assignment for schools and universities. A well-built timetable avoids room conflicts, respects teacher availability, and minimizes gaps in student schedules -- all while fitting within limited time slots and classrooms.

When to Use This Guide

This guide is a good fit when you need to:

  • Course timetabling -- assign courses to time slots so that no student or teacher has overlapping classes
  • Room assignment -- match courses to rooms with the right capacity and equipment (e.g., lab, lecture hall)
  • Teacher scheduling -- respect instructor availability windows and maximum teaching loads
  • Student-course matching -- assign students to course sections while balancing section sizes

If your problem involves assigning courses, rooms, and instructors to time slots under conflict and capacity rules, start here.

Step-by-Step Walkthrough

  1. List your courses. For each course, note the number of enrolled students, required room type, and how many sessions per week it needs.

  2. Define your resources. List available rooms with capacities and time slots across the week.

  3. Identify conflicts. Courses sharing students or the same instructor cannot be scheduled at the same time. Build a conflict list.

  4. Set constraints. Room capacity must be sufficient. Each room can host at most one course per time slot. Teacher availability must be respected.

  5. Choose your objective. Minimize the total number of student schedule gaps, minimize room changes for instructors, or simply find any feasible schedule.

  6. Run and interpret. The solver returns a course-to-(room, time slot) assignment. Review for practical adjustments.

Example: University Course Scheduling

Schedule 12 courses across 5 rooms and 4 daily time slots (8am, 10am, 1pm, 3pm). Each course needs exactly one slot. Rooms have different capacities. Several course pairs conflict because they share students.

import httpx

API_URL = "https://api.jaot.io/api/v2"
headers = {"Authorization": "Bearer ok_live_your_key_here"}

courses = [f"course_{i}" for i in range(1, 13)]
rooms = {"room_A": 120, "room_B": 80, "room_C": 60, "room_D": 40, "room_E": 30}
slots = ["8am", "10am", "1pm", "3pm"]

# Enrollment per course
enrollment = {f"course_{i}": 20 + i * 8 for i in range(1, 13)}

# Conflicting course pairs (share students)
conflicts = [
    ("course_1", "course_3"), ("course_2", "course_5"),
    ("course_4", "course_7"), ("course_6", "course_9"),
    ("course_8", "course_10"), ("course_11", "course_12"),
]

# Binary: assign course c to room r at time slot s
variables = [
    {"name": f"{c}_{r}_{s}", "type": "binary"}
    for c in courses for r in rooms for s in slots
]

# Objective: minimize unused capacity (pack courses into right-sized rooms)
objective = {
    "sense": "minimize",
    "coefficients": {
        f"{c}_{r}_{s}": max(0, rooms[r] - enrollment[c])
        for c in courses for r in rooms for s in slots
    },
}

constraints = []

# Each course assigned to exactly one room-slot
for c in courses:
    constraints.append({
        "name": f"assign_{c}",
        "coefficients": {
            f"{c}_{r}_{s}": 1 for r in rooms for s in slots
        },
        "sense": "==",
        "rhs": 1,
    })

# Room capacity: only assign if room fits enrollment
for c in courses:
    for r in rooms:
        if rooms[r] < enrollment[c]:
            for s in slots:
                constraints.append({
                    "name": f"capacity_{c}_{r}_{s}",
                    "coefficients": {f"{c}_{r}_{s}": 1},
                    "sense": "==",
                    "rhs": 0,
                })

# No two courses in the same room at the same time
for r in rooms:
    for s in slots:
        constraints.append({
            "name": f"room_{r}_{s}",
            "coefficients": {f"{c}_{r}_{s}": 1 for c in courses},
            "sense": "<=",
            "rhs": 1,
        })

# Conflicting courses cannot share a time slot
for (c1, c2) in conflicts:
    for s in slots:
        constraints.append({
            "name": f"conflict_{c1}_{c2}_{s}",
            "coefficients": {
                **{f"{c1}_{r}_{s}": 1 for r in rooms},
                **{f"{c2}_{r}_{s}": 1 for r in rooms},
            },
            "sense": "<=",
            "rhs": 1,
        })

response = httpx.post(f"{API_URL}/solve", headers=headers, json={
    "variables": variables,
    "objective": objective,
    "constraints": constraints,
})
result = response.json()

print(f"Status: {result['status']}")
print(f"Total wasted capacity: {result['objective_value']:.0f} seats")
for c in courses:
    for r in rooms:
        for s in slots:
            if result['solution'][f"{c}_{r}_{s}"] > 0.5:
                print(f"  {c} -> {r} at {s} (enrolled: {enrollment[c]}, capacity: {rooms[r]})")

The solver places each course in the smallest room that fits its enrollment and avoids time conflicts between courses that share students.

Next Steps