Spaces:
Sleeping
Sleeping
File size: 8,209 Bytes
5f76176 f0331f2 5f76176 f0331f2 5f76176 f0331f2 5f76176 f0331f2 5f76176 f0331f2 5f76176 f0331f2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | # Automatic Time Table Generation Agent (Genetic Algorithm)
# A Gradio app that demonstrates timetable generation using a genetic algorithm.
import random
import copy
from dataclasses import dataclass
from typing import List, Tuple, Dict
import gradio as gr
# --- Problem definition (simple, configurable) ---
DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri"]
SLOTS_PER_DAY = 6
@dataclass
class Course:
id: str
teacher: str
size: int
@dataclass
class Room:
id: str
capacity: int
# Example default dataset
DEFAULT_COURSES = [Course(f"C{i+1}", f"T{(i%4)+1}", random.choice([20,30,40])) for i in range(6)]
DEFAULT_ROOMS = [Room(f"R{i+1}", cap) for i,cap in enumerate([30,40,50,35,25])]
# Genome representation: list of assignments where each assignment = (course_id, day, slot, room_id)
def random_assignment(courses: List[Course], rooms: List[Room]) -> List[Tuple[str,int,int,str]]:
assignments = []
for c in courses:
day = random.randrange(len(DAYS))
slot = random.randrange(SLOTS_PER_DAY)
room = random.choice(rooms).id
assignments.append((c.id, day, slot, room))
return assignments
# Fitness function: penalize room capacity violations and teacher conflicts
def fitness(genome: List[Tuple[str,int,int,str]], courses: List[Course], rooms: List[Room]) -> int:
score = 0
room_caps = {r.id: r.capacity for r in rooms}
teacher_map = {c.id: c.teacher for c in courses}
course_size = {c.id: c.size for c in courses}
# Room capacity penalty
for course_id, day, slot, room_id in genome:
if course_size.get(course_id, 0) > room_caps.get(room_id, 0):
score -= (course_size[course_id] - room_caps.get(room_id, 0))
# Teacher conflict penalty (same teacher at same day+slot)
schedule_map = {}
for course_id, day, slot, room_id in genome:
teacher = teacher_map.get(course_id, None)
if teacher is None:
continue
key = (teacher, day, slot)
if key in schedule_map:
score -= 50
else:
schedule_map[key] = course_id
# Room double-booking penalty
room_map = {}
for course_id, day, slot, room_id in genome:
key = (room_id, day, slot)
if key in room_map:
score -= 40
else:
room_map[key] = course_id
# Soft reward for spreading same teacher across more days
teacher_days = {}
for course_id, day, slot, room_id in genome:
teacher = teacher_map.get(course_id, None)
if teacher:
teacher_days.setdefault(teacher, set()).add(day)
for t, days in teacher_days.items():
score += len(days)
return score
# GA operators
def crossover(a: List[Tuple], b: List[Tuple]) -> List[Tuple]:
if len(a) < 2:
return copy.deepcopy(a)
idx = random.randrange(1, len(a))
child = a[:idx] + b[idx:]
return child
def mutate(genome: List[Tuple], courses: List[Course], rooms: List[Room], mutation_rate=0.1) -> List[Tuple]:
new = copy.deepcopy(genome)
for i in range(len(new)):
if random.random() < mutation_rate:
course_id, day, slot, room = new[i]
if random.random() < 0.33:
day = random.randrange(len(DAYS))
if random.random() < 0.33:
slot = random.randrange(SLOTS_PER_DAY)
if random.random() < 0.5:
room = random.choice(rooms).id
new[i] = (course_id, day, slot, room)
return new
def tournament_select(scored, k=5):
participants = random.sample(scored, min(k, len(scored)))
participants.sort(key=lambda x: x[0], reverse=True)
return copy.deepcopy(participants[0][1])
def run_ga(courses: List[Course], rooms: List[Room], pop_size=200, generations=200, elite_frac=0.05, mutation_rate=0.1):
# initialize population
population = [random_assignment(courses, rooms) for _ in range(pop_size)]
best = None
for gen in range(generations):
scored = [(fitness(ind, courses, rooms), ind) for ind in population]
scored.sort(key=lambda x: x[0], reverse=True)
if best is None or scored[0][0] > best[0]:
best = (scored[0][0], copy.deepcopy(scored[0][1]))
# selection (elitism + tournament)
elites_count = max(1, int(pop_size * elite_frac))
elites = [copy.deepcopy(ind) for _, ind in scored[:elites_count]]
new_pop = elites.copy()
while len(new_pop) < pop_size:
a = tournament_select(scored)
b = tournament_select(scored)
child = crossover(a, b)
child = mutate(child, courses, rooms, mutation_rate)
new_pop.append(child)
population = new_pop
return best
# Helper to pretty-print timetable
def format_timetable(genome: List[Tuple[str,int,int,str]]) -> str:
table = { (d,s): [] for d in range(len(DAYS)) for s in range(SLOTS_PER_DAY)}
for course_id, day, slot, room_id in genome:
table[(day,slot)].append(f"{course_id}({room_id})")
lines = []
hdr = ["Slot\\Day"] + DAYS
lines.append("\t".join(hdr))
for s in range(SLOTS_PER_DAY):
row = [f"S{s+1}"]
for d in range(len(DAYS)):
items = table[(d,s)]
row.append(", ".join(items) if items else "-")
lines.append("\t".join(row))
return "\n".join(lines)
# Parsing helpers
def parse_courses(text: str) -> List[Course]:
# expects lines like: C1,T1,30
lines = [l.strip() for l in text.splitlines() if l.strip()]
out = []
for ln in lines:
parts = [p.strip() for p in ln.split(",")]
if len(parts) >= 3:
try:
out.append(Course(parts[0], parts[1], int(parts[2])))
except ValueError:
# skip malformed sizes
continue
return out
def parse_rooms(text: str) -> List[Room]:
# expects lines like: R1,40
lines = [l.strip() for l in text.splitlines() if l.strip()]
out = []
for ln in lines:
parts = [p.strip() for p in ln.split(",")]
if len(parts) >= 2:
try:
out.append(Room(parts[0], int(parts[1])))
except ValueError:
continue
return out
def generate_handler(courses_text, rooms_text, pop_size, generations, mutation_rate):
courses = parse_courses(courses_text)
rooms = parse_rooms(rooms_text)
if not courses:
courses = DEFAULT_COURSES
if not rooms:
rooms = DEFAULT_ROOMS
best = run_ga(courses, rooms, int(pop_size), int(generations), elite_frac=0.05, mutation_rate=float(mutation_rate))
score, genome = best
timetable = format_timetable(genome)
assignments = "\n".join([f"{c}@{DAYS[d]} S{slot+1} in {r}" for c,d,slot,r in genome])
return f"Fitness: {score}\n\nTimetable:\n{timetable}", assignments
# Gradio UI
with gr.Blocks(title="Automatic Time Table Generation Agent (Genetic Algorithm)") as demo:
gr.Markdown("# Automatic Time Table Generation Agent (Genetic Algorithm)")
with gr.Row():
with gr.Column(scale=2):
courses_input = gr.Textbox(lines=8, label="Courses (format: id,teacher,size)",
value='\n'.join([f"{c.id},{c.teacher},{c.size}" for c in DEFAULT_COURSES]))
rooms_input = gr.Textbox(lines=6, label="Rooms (format: id,capacity)",
value='\n'.join([f"{r.id},{r.capacity}" for r in DEFAULT_ROOMS]))
pop_size = gr.Slider(10, 500, value=200, step=10, label="Population Size")
generations = gr.Slider(10, 1000, value=200, step=10, label="Generations")
mutation_rate = gr.Slider(0.0, 1.0, value=0.1, step=0.01, label="Mutation Rate")
run_btn = gr.Button("Generate Timetable")
with gr.Column(scale=1):
out_text = gr.Textbox(lines=20, label="Result (timetable)")
out_assign = gr.Textbox(lines=12, label="Assignments (compact)")
run_btn.click(generate_handler, inputs=[courses_input, rooms_input, pop_size, generations, mutation_rate], outputs=[out_text, out_assign])
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)
|