GiGi2k5 commited on
Commit
48eaef5
·
1 Parent(s): e4e5ab3

first version

Browse files
Files changed (1) hide show
  1. app.py +208 -0
app.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from ortools.sat.python import cp_model
3
+ import concurrent.futures
4
+
5
+ def solve_exam_schedule(exams_text, rooms_text, num_slots, conflicts_text, availability_text):
6
+ # --- 1. Parsing des données ---
7
+ allowed_filiere = {"IA", "SEIOT", "IM", "SI", "GL"}
8
+ exams = []
9
+ for line in exams_text.strip().splitlines():
10
+ if line.strip() == "":
11
+ continue
12
+ parts = line.split(',')
13
+ if len(parts) < 4:
14
+ return "Erreur dans la saisie des examens. Chaque ligne doit comporter 4 valeurs : exam_id, nb_etudiants, durée, filière."
15
+ exam_id = int(parts[0].strip())
16
+ nb_students = int(parts[1].strip())
17
+ duration = int(parts[2].strip())
18
+ filiere = parts[3].strip()
19
+ if filiere not in allowed_filiere:
20
+ return f"Erreur : la filière '{filiere}' n'est pas autorisée. Les filières autorisées sont : {', '.join(allowed_filiere)}."
21
+ exams.append((exam_id, nb_students, duration, filiere))
22
+ num_exams = len(exams)
23
+
24
+ exam_id_to_index = {exam[0]: idx for idx, exam in enumerate(exams)}
25
+
26
+ rooms = []
27
+ for line in rooms_text.strip().splitlines():
28
+ if line.strip() == "":
29
+ continue
30
+ parts = line.split(',')
31
+ room_id = int(parts[0].strip())
32
+ capacity = int(parts[1].strip())
33
+ rooms.append((room_id, capacity))
34
+ num_rooms = len(rooms)
35
+
36
+ user_conflicts = []
37
+ for line in conflicts_text.strip().splitlines():
38
+ if line.strip() == "":
39
+ continue
40
+ parts = line.split(',')
41
+ try:
42
+ e1 = exam_id_to_index[int(parts[0].strip())]
43
+ e2 = exam_id_to_index[int(parts[1].strip())]
44
+ user_conflicts.append((e1, e2))
45
+ except KeyError:
46
+ return "Erreur dans les conflits. Vérifiez que les exam_id existent dans la liste des examens."
47
+
48
+ auto_conflicts = []
49
+ for i in range(num_exams):
50
+ for j in range(i + 1, num_exams):
51
+ if exams[i][3] == exams[j][3]:
52
+ auto_conflicts.append((i, j))
53
+
54
+ all_conflicts = set(user_conflicts + auto_conflicts)
55
+
56
+ availability = {}
57
+ for line in availability_text.strip().splitlines():
58
+ if line.strip() == "":
59
+ continue
60
+ parts = line.split(',')
61
+ room_id = int(parts[0].strip())
62
+ slots = [int(s.strip()) for s in parts[1:]]
63
+ availability[room_id] = slots
64
+ avail_matrix = []
65
+ for room in rooms:
66
+ room_id = room[0]
67
+ if room_id in availability:
68
+ avail_matrix.append(availability[room_id])
69
+ else:
70
+ avail_matrix.append([1]*num_slots)
71
+
72
+ # --- 2. Modélisation avec OR-Tools ---
73
+ model = cp_model.CpModel()
74
+
75
+ x = {}
76
+ for i in range(num_exams):
77
+ for j in range(num_slots):
78
+ for k in range(num_rooms):
79
+ x[(i, j, k)] = model.NewBoolVar(f'x_{i}_{j}_{k}')
80
+
81
+ for i in range(num_exams):
82
+ model.Add(sum(x[(i, j, k)] for j in range(num_slots) for k in range(num_rooms)) == 1)
83
+
84
+ for j in range(num_slots):
85
+ for k in range(num_rooms):
86
+ model.Add(
87
+ sum(exams[i][1] * x[(i, j, k)] for i in range(num_exams))
88
+ <= rooms[k][1]
89
+ )
90
+
91
+ for (i, l) in all_conflicts:
92
+ for j in range(num_slots):
93
+ model.Add(
94
+ sum(x[(i, j, k)] for k in range(num_rooms)) +
95
+ sum(x[(l, j, k)] for k in range(num_rooms))
96
+ <= 1
97
+ )
98
+
99
+ for j in range(num_slots):
100
+ for k in range(num_rooms):
101
+ if avail_matrix[k][j] == 0:
102
+ for i in range(num_exams):
103
+ model.Add(x[(i, j, k)] == 0)
104
+
105
+ T_max = model.NewIntVar(0, num_slots - 1, 'T_max')
106
+ y = {}
107
+ for i in range(num_exams):
108
+ for j in range(num_slots):
109
+ y[(i, j)] = model.NewBoolVar(f'y_{i}_{j}')
110
+ model.Add(sum(x[(i, j, k)] for k in range(num_rooms)) == y[(i, j)])
111
+
112
+ for i in range(num_exams):
113
+ for j in range(num_slots):
114
+ model.Add(T_max >= j).OnlyEnforceIf(y[(i, j)])
115
+
116
+ model.Minimize(T_max)
117
+
118
+ solver = cp_model.CpSolver()
119
+ # Limite de temps pour éviter un blocage trop long
120
+ solver.parameters.max_time_in_seconds = 10
121
+ status = solver.Solve(model)
122
+
123
+ if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
124
+ schedule = []
125
+ for i in range(num_exams):
126
+ for j in range(num_slots):
127
+ for k in range(num_rooms):
128
+ if solver.Value(x[(i, j, k)]) == 1:
129
+ exam_id = exams[i][0]
130
+ nb_students = exams[i][1]
131
+ duration = exams[i][2]
132
+ filiere = exams[i][3]
133
+ room_id = rooms[k][0]
134
+ schedule.append({
135
+ "Examen": exam_id,
136
+ "Filière": filiere,
137
+ "Créneau": j,
138
+ "Salle": room_id,
139
+ "Nb Étudiants": nb_students,
140
+ "Durée (h)": duration
141
+ })
142
+ schedule = sorted(schedule, key=lambda item: item["Créneau"])
143
+ output_text = "### Calendrier des Examens\n"
144
+ for item in schedule:
145
+ output_text += (
146
+ f"**Examen {item['Examen']}** | Filière: {item['Filière']} | Créneau: {item['Créneau']} | "
147
+ f"Salle: {item['Salle']} | Étudiants: {item['Nb Étudiants']} | Durée: {item['Durée (h)']}h\n\n"
148
+ )
149
+ return output_text
150
+ else:
151
+ return "Aucune solution trouvée."
152
+
153
+ # Utilisation d'un ThreadPoolExecutor pour exécuter la fonction dans un thread séparé
154
+ executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
155
+
156
+ def solve_in_thread(*args, **kwargs):
157
+ future = executor.submit(solve_exam_schedule, *args, **kwargs)
158
+ return future.result()
159
+
160
+ with gr.Blocks() as demo:
161
+ gr.Markdown("# Planification des Examens avec Filières (IA, SEIOT, IM, SI, GL)")
162
+ gr.Markdown(
163
+ "Remplissez les champs ci-dessous pour définir le problème de planification des examens. "
164
+ "Pour les examens, chaque ligne doit être sous la forme :<br>"
165
+ "`exam_id, nb_etudiants, durée, filière`<br>"
166
+ "Exemple: `0, 30, 2, IA`"
167
+ )
168
+
169
+ with gr.Row():
170
+ exams_input = gr.Textbox(
171
+ label="Liste des examens",
172
+ lines=5,
173
+ value="0, 30, 2, IA\n1, 25, 1, SEIOT\n2, 40, 2, IA\n3, 20, 1, SI"
174
+ )
175
+ with gr.Row():
176
+ rooms_input = gr.Textbox(
177
+ label="Liste des salles (chaque ligne: room_id, capacité)",
178
+ lines=2,
179
+ value="0, 50\n1, 30"
180
+ )
181
+ with gr.Row():
182
+ num_slots_input = gr.Slider(
183
+ label="Nombre de créneaux", minimum=1, maximum=10, step=1, value=4
184
+ )
185
+ with gr.Row():
186
+ conflicts_input = gr.Textbox(
187
+ label="Conflits supplémentaires (chaque ligne: exam1, exam2) [optionnel]",
188
+ lines=2,
189
+ value="1, 3"
190
+ )
191
+ with gr.Row():
192
+ availability_input = gr.Textbox(
193
+ label=("Disponibilité des salles (chaque ligne: room_id, slot1, slot2, ... "
194
+ "avec 1 pour dispo, 0 sinon)"),
195
+ lines=2,
196
+ value="0, 1, 1, 1, 1\n1, 1, 1, 0, 1"
197
+ )
198
+
199
+ output = gr.Markdown(label="Calendrier des examens")
200
+
201
+ solve_btn = gr.Button("Planifier les examens")
202
+ solve_btn.click(
203
+ fn=solve_in_thread,
204
+ inputs=[exams_input, rooms_input, num_slots_input, conflicts_input, availability_input],
205
+ outputs=output
206
+ )
207
+
208
+ demo.launch()