musawar32ali commited on
Commit
f0331f2
·
verified ·
1 Parent(s): a21bb05

Create app.py

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