-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathUtils.py
More file actions
2000 lines (1561 loc) · 83.8 KB
/
Copy pathUtils.py
File metadata and controls
2000 lines (1561 loc) · 83.8 KB
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
# coding: utf-8
# In[ ]:
import numpy as np
import pandas as pd
import math
import re, io, base64
from collections import defaultdict, OrderedDict
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib import colors
import pyomo.environ as pyo
from pyomo.opt import SolverFactory
from IPython.display import FileLink, Image, display, HTML
import warnings
warnings.simplefilter(action='ignore', category=UserWarning)
# In[ ]:
def what_semester(input_file_path):
sheetnames = dict()
df = pd.read_excel(input_file_path, sheet_name='Info')
semester = df.loc[:, 'Semester'].dropna().item()
if semester =='Fall' or semester == 'fall':
sheetnames['OurCourses'] = 'Fall Courses'
sheetnames['MathChem'] = 'Fall Math&Chem'
sheetnames['Conflicting'] = 'Fall Conflicting'
sheetnames['Labs'] = 'Fall Labs'
sheetnames['NonTrad'] = 'Fall Non-Trad'
sheetnames['DesiredTimes'] = 'Fall Desired Times'
sheetnames['UndesiredTimes'] = 'Fall Undesired Times'
elif semester == 'Spring' or semester == 'spring':
sheetnames['OurCourses'] = 'Spring Courses'
sheetnames['MathChem'] = 'Spring Math&Chem'
sheetnames['Conflicting'] = 'Spring Conflicting'
sheetnames['Labs'] = 'Spring Labs'
sheetnames['NonTrad'] = 'Spring Non-Trad'
sheetnames['DesiredTimes'] = 'Spring Desired Times'
sheetnames['UndesiredTimes'] = 'Spring Undesired Times'
else:
return print('Check the data you entered for the semester in the "Info" worksheet')
return sheetnames
# In[1]:
def read_data(input_file_path, sheetnames):
for key, value in sheetnames.items():
globals()[key] = value
data = dict()
# Dictionary of time slot info
time_slot_info = {}
# Read the Excel file into a DataFrame
df = pd.read_excel(input_file_path, sheet_name='Info')
# Extract data from the first column and remove NaN values
times = df.loc[:, 'Time slots'].dropna().tolist()
for slot in times:
# Extract day, start time, and duration from the slot
d, s, u = re.search(r'([A-Za-z]+)(\d+)-(\d+)', slot).groups()
# Convert start time to float
s = float(s)
# Adjust start and end time if the start time is 15 or 18 (it is 15:30 or 18:30)
if s == 15 or s == 18:
s += 0.5
# Calculate end time
e = s + int(u) / 60
# Create an inner dictionary for the time slot
info = {'M': 1 if 'M' in d else 0,
'T': 1 if 'T' in d else 0,
'W': 1 if 'W' in d else 0,
'R': 1 if 'R' in d else 0,
'F': 1 if 'F' in d else 0,
'duration': u,
'start': s,
'end': e
}
# Add the inner dictionary to the outer dictionary
time_slot_info[slot] = info
data['time_slot_info'] = time_slot_info
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of courses info (engr, phys, math, chem)
df1 = pd.read_excel(input_file_path, sheet_name=OurCourses)
df2 = pd.read_excel(input_file_path, sheet_name=MathChem)
# Convert the DataFrame to a nested dictionary
courses_info_dict = {}
for index, row in df1.iterrows():
key = row['Item']
inner_dict = {}
for column in df1.columns[1:8]:
inner_dict[column] = row[column]
courses_info_dict[key] = inner_dict
for index, row in df2.iterrows():
key = row['Item']
inner_dict = {}
for column in df2.columns[1:5]:
inner_dict[column] = row[column]
inner_dict['Duration'] = f"{row['Course']}-u"
courses_info_dict[key] = inner_dict
data['courses_info_dict'] = courses_info_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Department meetings durations
df = pd.read_excel(input_file_path, sheet_name='Info')
# Extract data and remove NaN values
meetings_duration = df.loc[:, 'Meetings'].dropna().item()
data['meetings_duration'] = meetings_duration
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of meeting times -> Set M
M_list = []
# Days of the week with their respective end times
days_end_hours = {'M': 17, 'T': 17, 'W': 17, 'R': 17, 'F': 14}
# Generate the times
for d, e in days_end_hours.items():
h = 8.0 # Start time
while h + meetings_duration <= e:
M_list.append(f"{d}{h}")
h += meetings_duration
data['M_list'] = M_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of meeting times info
meeting_info = {}
for m in M_list:
# Extract day and start time
match = re.match(r'([A-Z]+)(\d+(\.\d+)?)$', m)
if match:
d, start_str, _ = match.groups()
# Convert start time to float
s = float(start_str)
# Calculate end time (start time + meeting duration - 1 minute)
e = s + meetings_duration - 1 / 60
# Create an inner dictionary for the time slot
info = {
'M': 1 if 'M' in d else 0,
'T': 1 if 'T' in d else 0,
'W': 1 if 'W' in d else 0,
'R': 1 if 'R' in d else 0,
'F': 1 if 'F' in d else 0,
'duration': meetings_duration * 60, # Duration in minutes
'start': s,
'end': e
}
# Add to the dictionary
meeting_info[m] = info
data['meeting_info'] = meeting_info
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of all time slots -> set T
# We have already read time slots from the Excel file and stored it in times
T_list = times
data['T_list'] = T_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of 1-hr time blocks -> set B
B_list = []
# Days of the week with their respective end times
days_end_hours = {'M': 18, 'T': 18, 'W': 18, 'R': 18, 'F': 14}
# Generate the times
for d, e in days_end_hours.items():
# Ensure the last block does not exceed the end time
for h in range(8, e):
if h + 1 > e:
break
B_list.append(f'{d}{h}')
data['B_list'] = B_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of 1-hr block times info
block_info = {}
for m in B_list:
# Extract the day and start time from the block times
d, s = re.search(r'([A-Z]+)(\d+)', m).groups()
# Convert start time to float
s = float(s)
# Calculate end time (start time + 59 minutes)
e = s + 59/60
# Create an inner dictionary for the time slot
info = {'M': 1 if 'M' in d else 0,
'T': 1 if 'T' in d else 0,
'W': 1 if 'W' in d else 0,
'R': 1 if 'R' in d else 0,
'F': 1 if 'F' in d else 0,
'duration': meetings_duration*60,
'start': s,
'end': e
}
# Add the inner dictionary to the outer dictionary
block_info[m] = info
data['block_info'] = block_info
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of courses -> set J
J_list = []
# Iterate over the values of the courses_info_dict dictionary
for c in courses_info_dict.values():
# Extract the desired value (e.g., 'Course' or 'Professor') and add it to the list
J_list.append(c['Course'])
# Remove duplicate values by converting the list to a set
J_list = list(set(J_list))
data['J_list'] = J_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of sections -> set S
df = pd.read_excel(input_file_path, sheet_name='Info')
# Extract data from the first column and remove NaN values
S_list = df.loc[:, 'Sections'].dropna().tolist()
data['S_list'] = S_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of weekdays -> set D
df = pd.read_excel(input_file_path, sheet_name='Info')
# Extract data from the first column and remove NaN values
D_list = df.loc[:, 'Days'].dropna().tolist()
data['D_list'] = D_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of class durations (set U) in this format: 'duration - number of days in a week'
df1 = pd.read_excel(input_file_path, sheet_name='Info')
df2 = pd.read_excel(input_file_path, sheet_name=MathChem)
# Extract data from the first column and remove NaN values
U_ourcourses = df1['Durations'].dropna().tolist()
U_mathchem = df2['Course'].unique().tolist()
U_mathchem = [f"{item}-u" for item in U_mathchem]
U_list = U_ourcourses + U_mathchem
data['U_list'] = U_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of professors -> set P
P_list = []
# Iterate over the values of the courses_info_dict dictionary
for p in courses_info_dict.values():
# Check if the 'Professor' key exists in the inner dictionary
if 'Professor1' in p:
# Extract the professor's name and add it to the list
P_list.append(p['Professor1'])
# Remove duplicate values by converting the list to a set
P_list = sorted(list(set(P_list)))
data['P_list'] = P_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of preferences for what time a day to teach -> set R
df = pd.read_excel(input_file_path, sheet_name='Info')
# Extract data from the first column and remove NaN values
R_list = df.loc[:, 'Preferences'].dropna().tolist()
data['R_list'] = R_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of courses and sections -> set JS
JS_list = []
# Iterate over the items of courses_info_dict
for i, c_info in courses_info_dict.items():
c = c_info['Course']
s = c_info['Section']
# Append the course and section as a tuple to the list
JS_list.append((c, s))
data['JS_list'] = JS_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a dictionary where course names are keys and sections are values -> set JS
JS_dict = {}
for k, d in courses_info_dict.items():
c = d['Course']
s = d['Section']
# If the course is not already in the dictionary, initialize it as an empty list
if c not in JS_dict:
JS_dict[c] = []
# Append the section to the course
JS_dict[c].append(s)
data['JS_dict'] = JS_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of courses, sections, and durations for our courses -> set JSU
JSU_dict = {}
# Iterate over the items of courses_info_dict
for u in courses_info_dict.values():
# Check if the 'Duration' key exists in the inner dictionary
if 'Duration' in u:
c = u['Course']
s = u['Section']
d = u['Duration']
# Append the course and section as a tuple to the list
JSU_dict[(c, s)] = [d]
data['JSU_dict'] = JSU_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary to store union of time slots for each course j -> set JU(j)
JU_dict = defaultdict(set)
# Iterate over the original dictionary
for (c, s), t in JSU_dict.items():
JU_dict[c].update(t)
# Convert sets to lists
JU_dict = {c: list(t) for c, t in JU_dict.items()}
data['JU_dict'] = JU_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of professors who don't want to teach in a specific day -> set PD
df = pd.read_excel(input_file_path, sheet_name='Undesired Days')
# Filter the DataFrame to include only rows where 'Not desirable day' is not empty
filtered_df = df.dropna(subset=['Not desirable day'])
# Create a professor day list from the DataFrame
PD_list = list(filtered_df[['Professors',
'Not desirable day']].itertuples(index=False,
name=None))
data['PD_list'] = PD_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of courses that desired to be scheduled at a more desirable time -> set JST1
df = pd.read_excel(input_file_path, sheet_name=DesiredTimes)
# Create a preferred time list from the DataFrame
JST1_list = list(df[['Course',
'Section',
'Time']].itertuples(index=False, name=None))
data['JST1_list'] = JST1_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of courses that are not desired to be scheduled at a certain time -> set JST2
df = pd.read_excel(input_file_path, sheet_name=UndesiredTimes)
# Create an avoid time list from the DataFrame
JST2_list = list(df[['Course',
'Section',
'Time']].itertuples(index=False, name=None))
data['JST2_list'] = JST2_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of Math and Chem courses with fixed schedule -> set JST3
# Read the Excel file
df = pd.read_excel(input_file_path, sheet_name=MathChem)
# Create a fixed time list from the DataFrame
JST3_list = list(df[['Course',
'Section',
'Time']].itertuples(index=False, name=None))
data['JST3_list'] = JST3_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of professors and their courses -> set PJ(p)
PJ_dict = {}
# Iterate over the items of courses_info_dict
for item in courses_info_dict.values():
# Check if the 'Professor' key exists in the inner dictionary
if 'Professor1' in item:
p1 = item['Professor1']
p2 = item['Professor2']
c = item['Course']
s = item['Section']
# Add Professor1 and their courses to PJ_dict
if p1: # Ensure p1 is not None or empty
if p1 in PJ_dict:
PJ_dict[p1].append((c, s))
else:
PJ_dict[p1] = [(c, s)]
# Add Professor2 and their courses to PJ_dict, skipping if it's nan
if p2 and not (isinstance(p2, float) and math.isnan(p2)): # Skip if p2 is nan
if p2 in PJ_dict:
PJ_dict[p2].append((c, s))
else:
PJ_dict[p2] = [(c, s)]
data['PJ_dict'] = PJ_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of time slots for duration u -> set UT(u)
UT_dict = {'50min-1': [i for i in times if '50' in i and '150' not in i and i.find(next(filter(str.isdigit, i))) == 1],
'50min-2': [i for i in times if '50' in i and i.find(next(filter(str.isdigit, i))) == 2],
'50min-3': [i for i in times if '50' in i and i.find(next(filter(str.isdigit, i))) == 3],
'75min-1': [i for i in times if '75' in i and i.find(next(filter(str.isdigit, i))) == 1],
'75min-2': [i for i in times if '75' in i and i.find(next(filter(str.isdigit, i))) == 2],
'75min-3': [i for i in times if '75' in i and i.find(next(filter(str.isdigit, i))) == 3],
'100min-1': [i for i in times if '100' in i and i.find(next(filter(str.isdigit, i))) == 1],
'110min-1': [i for i in times if '110' in i and i.find(next(filter(str.isdigit, i))) == 1],
'150min-1': [i for i in times if '150' in i and i.find(next(filter(str.isdigit, i))) == 1],
'170min-1': [i for i in times if '170' in i and i.find(next(filter(str.isdigit, i))) == 1],
}
# Add math and chem durations to the dictionary
df = pd.read_excel(input_file_path, sheet_name=MathChem)
# Group by course name and collect unique time slots
MC_times = df.groupby('Course')['Time'].unique().apply(list).to_dict()
# Update keys to include '-u'
MC_times = {f"{key}-u": value for key, value in MC_times.items()}
UT_dict.update(MC_times)
# MC_times = list(df[['Duration',
# 'Time']].itertuples(index=False, name=None))
# for k, v in MC_times:
# UT_dict[k] = [v]
data['UT_dict'] = UT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of all available time slots for course section (j,s) -> set JST(j,s)
JST_dict = {k: UT_dict[v[0]] for k, v in JSU_dict.items()}
# # Add math and chem durations to the dictionary
# df = pd.read_excel(input_file_path, sheet_name=MathChem)
# # Group by course name and collect unique time slots
# MC_times = df.groupby('Course')['Time'].unique().apply(list).to_dict()
# # Update keys to include '-u'
# MC_times = {f"{key}-u": value for key, value in MC_times.items()}
# Adding math and chem courses to JST_dict
# for (j, s, t) in JST3_list:
# JST_dict[(j, s)] = [t]
data['JST_dict'] = JST_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary to store union of time slots for each course j -> set JT(j)
JT_dict = defaultdict(set)
# Iterate over the original dictionary
for (c, s), t in JST_dict.items():
JT_dict[c].update(t)
# Convert sets to lists
JT_dict = {c: list(t) for c, t in JT_dict.items()}
data['JT_dict'] = JT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of professors and available time slots for their courses -> set PT(p)
PT_dict = {}
# Loop through each professor and their associated (j, s) pairs
for p, js_pairs in PJ_dict.items():
# Initialize an empty list to store unique time slots for professor p
ts = []
# Loop through each (j, s) pair for this professor
for js in js_pairs:
# Add all time slots from JST corresponding to this (j, s) pair
if js in JST_dict:
for t in JST_dict[js]:
if t not in ts: # Avoid duplicates while preserving order
ts.append(t)
# Assign the list of time slots to PT[p] without sorting
PT_dict[p] = ts
data['PT_dict'] = PT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of time slots for day d -> set DT(d)
DT_dict = {}
# Define a mapping of day initials to full day names
day_mapping = {'M': 'Mon', 'T': 'Tue', 'W': 'Wed', 'R': 'Thu', 'F': 'Fri'}
# Iterate over the list of times
for t in T_list:
# Extract the day initials using a regular expression
day_initials = re.search(r'[A-Za-z]+', t).group()
# Iterate over the extracted day initials
for i in day_initials:
# Map the day initial to the full day name
d = day_mapping.get(i)
# Check if the day already exists in the dictionary
if d in DT_dict:
# Append the time to the list of times for the corresponding day
DT_dict[d].append(t)
else:
# Create a new list with the time for the corresponding day
DT_dict[d] = [t]
data['DT_dict'] = DT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of preference r and their time slots belong to it -> set RT(r)
# Extract the start time of classes from time slots
start_times = [int(re.search(r'[A-Za-z]+(\d+)-', i).group(1))
for i in times if re.search(r'[A-Za-z]+(\d+)-', i)]
RT_dict = {'Morning': [i for (i, j) in zip(times, start_times) if j < 12],
'ExtendedMorning': [i for (i, j) in zip(times, start_times) if j < 14],
'WholeDay': [i for (i, j) in zip(times, start_times) if j <= 16],
'Midday': [i for (i, j) in zip(times, start_times) if j >= 10 and j <= 14],
'ExtendedMidday': [i for (i, j) in zip(times, start_times) if j >= 10 and j <= 16],
'Afternoon': [i for (i, j) in zip(times, start_times) if j >= 12 and j <= 16],
'Evening': [i for (i, j) in zip(times, start_times) if j >= 16 ]
}
data['RT_dict'] = RT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of professors and their preferred time to teach -> set PR(p)
# Read the Excel file into a Pandas DataFrame
df = pd.read_excel(input_file_path, sheet_name='Teaching Times')
# Create a dictionary with Professors as keys and Preferred time to teach as values
PR_dict = pd.Series(df['Preferred time to teach'].values,
index=df['Professors']).to_dict()
PR_dict = {p: [PR_dict[p]] for p in PR_dict}
data['PR_dict'] = PR_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of levels with incompatible courses -> set F
df = pd.read_excel(input_file_path, sheet_name=Conflicting)
# Extract the 'Group' column, drop any NaN values, and find unique values
F_list = df['Group'].dropna().unique()
data['F_list'] = F_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of incompatible courses -> set FJ(f)
df = pd.read_excel(input_file_path, sheet_name=Conflicting)
# Initialize a dictionary to store groups and their courses
FJ_dict = defaultdict(list)
# Track the current group while iterating through the rows
current_group = None
for index, row in df.iterrows():
group = row['Group']
course = row['Courses']
# If we encounter a new group, update the current group
if pd.notna(group):
current_group = group
# If there's a valid group and course, add the course to the group
if current_group and pd.notna(course):
FJ_dict[current_group].append(course)
# Convert defaultdict to a regular dictionary
FJ_dict = dict(FJ_dict)
data['FJ_dict'] = FJ_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of the number of courses in each group of incompatible courses -> set FN(f)
FN_dict = {g: len(cs) for g, cs in FJ_dict.items()}
data['FN_dict'] = FN_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of conflicted time slots -> set GT(g)
GT_dict = {}
for slot1, info1 in time_slot_info.items():
for slot2, info2 in time_slot_info.items():
# Skip if comparing the same slot or if they are on different days
if ((info1['M'] == 1 and info2['M'] == 1) or
(info1['T'] == 1 and info2['T'] == 1) or
(info1['W'] == 1 and info2['W'] == 1) or
(info1['R'] == 1 and info2['R'] == 1) or
(info1['F'] == 1 and info2['F'] == 1)):
# Check for conflicts based on start and end times
if (
(info1['start'] <= info2['start'] < info1['end']) or
(info1['start'] < info2['end'] <= info1['end']) or
(info2['start'] <= info1['start'] < info2['end']) or
(info2['start'] < info1['end'] <= info2['end']) ):
# Add conflicted slots to the conflicts dictionary
if slot1 in GT_dict:
GT_dict[slot1].append(slot2)
else:
GT_dict[slot1] = [slot2]
data['GT_dict'] = GT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of times conflicted with other time slots -> set G
G_list = []
# Iterate over the items of GT_dict
for item in GT_dict.keys():
G_list.append(item)
data['G_list'] = G_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Load Excel data into a Pandas DataFrame
df = pd.read_excel(input_file_path, sheet_name='Busy Times')
# Mapping full day names to shorthand format
day_mapping = {'Mon': 'M', 'Tue': 'T', 'Wed': 'W', 'Thu': 'R', 'Fri': 'F'}
# Parse restrictions into a dictionary
restrictions = {}
for _, row in df.iterrows():
p = row['Professor']
d = row['Busy day']
s = row['From']
e = row['To']
if pd.notna(p) and pd.notna(d) and pd.notna(s) and pd.notna(e):
day_short = day_mapping.get(d, None) # Convert full day name to shorthand
if day_short:
if p not in restrictions:
restrictions[p] = {}
restrictions[p][day_short] = (s, e)
data['restrictions'] = restrictions
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of courses that cannot be at certain time slots (busy times for a professor) -> set JST4
JST4_list = []
for p, ds in restrictions.items():
if p in PJ_dict: # Skip professors without courses
for j, s in PJ_dict[p]:
if (j, s) in JST_dict:
for t in JST_dict[j, s]: # Times available for (j, s)
time_data = time_slot_info[t]
start_time = time_slot_info[t]['start']
end_time = time_slot_info[t]['end']
# Check if time slot t conflicts with restrictions
for d, is_active in time_data.items():
if is_active == 1 and d in ds: # Only consider active days and restricted days
restricted_start, restricted_end = ds[d]
# Check for overlap
if (restricted_start <= start_time < restricted_end or
restricted_start <= end_time < restricted_end):
JST4_list.append((j, s, t))
data['JST4_list'] = JST4_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# List of non-traditional courses that we may not want to consider them in the working span -> set JST5
df = pd.read_excel(input_file_path, sheet_name=NonTrad)
# Create a preferred time list from the DataFrame
JST5_list = list(df[['Course', 'Section']].itertuples(index=False, name=None))
data['JST5_list'] = JST5_list
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of conflicted time slots with block b -> set BT(b)
BT_dict = {}
for slot1, info1 in block_info.items():
for slot2, info2 in time_slot_info.items():
# Skip if comparing the same slot or if they are on different days
if ((info1['M'] == 1 and info2['M'] == 1) or
(info1['T'] == 1 and info2['T'] == 1) or
(info1['W'] == 1 and info2['W'] == 1) or
(info1['R'] == 1 and info2['R'] == 1) or
(info1['F'] == 1 and info2['F'] == 1)):
# Check for conflicts based on start and end times
if (
(info1['start'] <= info2['start'] < info1['end']) or
(info1['start'] < info2['end'] <= info1['end']) or
(info2['start'] <= info1['start'] < info2['end']) or
(info2['start'] < info1['end'] <= info2['end']) ):
# Add conflicted slots to the conflicts dictionary
if slot1 in BT_dict:
BT_dict[slot1].append(slot2)
else:
BT_dict[slot1] = [slot2]
data['BT_dict'] = BT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of conflicted time slots with block b -> set MT(m)
MT_dict = {}
for slot1, info1 in meeting_info.items():
for slot2, info2 in time_slot_info.items():
# Skip if comparing the same slot or if they are on different days
if ((info1['M'] == 1 and info2['M'] == 1) or
(info1['T'] == 1 and info2['T'] == 1) or
(info1['W'] == 1 and info2['W'] == 1) or
(info1['R'] == 1 and info2['R'] == 1) or
(info1['F'] == 1 and info2['F'] == 1)):
# Check for conflicts based on start and end times
if (
(info1['start'] <= info2['start'] < info1['end']) or
(info1['start'] < info2['end'] <= info1['end']) or
(info2['start'] <= info1['start'] < info2['end']) or
(info2['start'] < info1['end'] <= info2['end']) ):
# Add conflicted slots to the conflicts dictionary
if slot1 in MT_dict:
MT_dict[slot1].append(slot2)
else:
MT_dict[slot1] = [slot2]
data['MT_dict'] = MT_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionary of the courses or labs that need to be scheduled back to back on a same day
df = pd.read_excel(input_file_path, sheet_name=Labs)
# Initialize course_data dictionary
LS_dict = {}
# Iterate over rows of the DataFrame
for _, row in df.iterrows():
c = row['Course']
# Drop any NaN or blank values in the section's columns
s = [sec for sec in row[1:].values if pd.notna(sec)]
# Add course and sections to the dictionary
LS_dict[c] = {'sections': s, 'offsets': list(range(len(s)))}
data['LS_dict'] = LS_dict
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Dictionaries of max hours a day and max days a week
df = pd.read_excel(input_file_path, sheet_name='Teaching Load')
hrs_a_day = {}
# Build the dictionary from the DataFrame
max_hrs_dict = df.set_index('Professors')['Max hrs a day'].to_dict()
max_days_dict = df.set_index('Professors')['Max days a week'].to_dict()
# If there is no data in the dictionary, replace it with a default value
max_hrs_dict = {k: 10. if np.isnan(v) else v for k, v in max_hrs_dict.items()}
max_days_dict = {k: 5. if np.isnan(v) else v for k, v in max_days_dict.items()}
data['max_hrs_dict'] = max_hrs_dict
data['max_days_dict'] = max_days_dict
return (data)
# In[ ]:
def build_model(data):
for key, value in data.items():
globals()[key] = value
model = pyo.ConcreteModel() # create a concrete model using Pyomo
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# The indices for constraints and variables (Sets)
model.T = pyo.Set(initialize=T_list) # set of time slots (T)
model.B = pyo.Set(initialize=B_list) # set of block times (B)
model.J = pyo.Set(initialize=J_list) # set of courses (J)
model.S = pyo.Set(initialize=S_list) # set of sections (S)
model.D = pyo.Set(initialize=D_list) # set of weekdays (D)
model.U = pyo.Set(initialize=U_list) # set of class durations (U)
model.P = pyo.Set(initialize=P_list) # set of professors (P)
model.R = pyo.Set(initialize=R_list) # set of preference (R)
model.G = pyo.Set(initialize=G_list) # set of conflicted time slots (G)
model.F = pyo.Set(initialize=F_list) # set of incompatible course groups (F)
model.L = pyo.Set(initialize=LS_dict.keys()) # set of labs whose sections need to be back to back (L)
model.JST1 = pyo.Set(initialize=JST1_list) # set of courses wanted to be scheduled at certain times
model.JST2 = pyo.Set(initialize=JST2_list) # set of courses wanted not to be scheduled at certain times
model.JST3 = pyo.Set(initialize=JST3_list) # set of courses have to be scheduled at certain times
model.JST4 = pyo.Set(initialize=JST4_list) # set of courses cannot be scheduled at certain times (busy times)
model.JST5 = pyo.Set(initialize=JST5_list) # set of non traditional courses
model.PD = pyo.Set(initialize=PD_list) # set of professors and days they don't want to teach
model.JS1 = pyo.Set(initialize=JS_list) # set of courses and sections (tuple)
model.JS = pyo.Set(model.J, within=model.S, initialize=JS_dict) # set of courses and sections (dic)
model.LS = pyo.Set(model.L, within=model.S, initialize={l: data['sections'] for l, data in LS_dict.items()}) # set of sections for back to back courses
model.JSU= pyo.Set(model.JS1, within=model.U, initialize=JSU_dict) # set of our courses, sections, and durations
model.JU = pyo.Set(model.J, within=model.U, initialize=JU_dict) # set of all courses and possible durations
model.JST= pyo.Set(model.JS1, within=model.T, initialize=JST_dict) # set of all courses, sections, and possible times
model.JT = pyo.Set(model.J, within=model.T, initialize=JT_dict) # set of all courses and possible times
model.PJ = pyo.Set(model.P, within=model.JS1, initialize=PJ_dict) # set of courses for professor p
model.PT = pyo.Set(model.P, within=model.T, initialize=PT_dict) # set of time slots for professor p
model.PR = pyo.Set(model.P, within=model.R, initialize=PR_dict) # set of professors and their preferred time to teach
model.UT = pyo.Set(model.U, within=model.T, initialize=UT_dict) # set of time slots for duration u
model.DT = pyo.Set(model.D, within=model.T, initialize=DT_dict) # set of time slots in day d
model.RT = pyo.Set(model.R, within=model.T, initialize=RT_dict) # set of time slots for preference r
model.GT = pyo.Set(model.G, within=model.T, initialize=GT_dict) # set of times conflicted with a time slot
model.FJ = pyo.Set(model.F, within=model.J, initialize=FJ_dict) # set of conflicting courses in a semester
model.BT = pyo.Set(model.B, within=model.T, initialize=BT_dict) # set of blocks and time slots conflicts
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create the Pyomo parameter back to back courses offsets
# Flatten the LS_dict to create the offset dictionary
offset_data = {(c, s): data['offsets'][idx]
for c, data in LS_dict.items()
for idx, s in enumerate(data['sections'])}
model.q = pyo.Param(offset_data.keys(), # The keys are course-section pairs
initialize=offset_data,
within=pyo.NonNegativeIntegers) # Assuming offsets are non-negative
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define the parameters with [p, d] domain: h (max hrs a day), k (max days a week)
def max_hrs_init(model, p, d):
return max_hrs_dict[p] # Use professor-specific max hours
model.h = pyo.Param(model.P, model.D, initialize=max_hrs_init)
model.k = pyo.Param(model.P, initialize=max_days_dict)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create an empty list to hold the variable x indices
x_ind_list = []
# Loop through each (j, s) pair in JS and corresponding timeslots in JST
for (j, s) in JS_list:
for t in model.JST[(j, s)]:
# Initialize the variable for each valid (j, s, t) combination
x_ind_list.append((j, s, t))
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# A function to create variable y indices
def valid_index_y(model):
return ((f, j, t) for f in model.F for j in model.FJ[f] for t in model.JT[j])
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Nested dictionary for time slots {l: {s: {d: [t]}}}
time_slots_dict = defaultdict(lambda: defaultdict(dict))
# Populate the dictionary
for l in model.L: # Courses
for s in model.LS[l]: # Sections for the course
for d in model.D: # Days
# Filter time slots for the specific day and course-section pair
day_slots = [t for t in model.DT[d] if t in model.JST[l, s]]
time_slots_dict[l][s][d] = day_slots # Assign to the dictionary
time_slots_dict = dict(time_slots_dict)
# A function to create variable z indices
def valid_index_z(model):
valid_indices = []
for l in model.L: # Iterate over courses
for t in model.JT[l]: # Iterate over time slots for course l
day = next(d for d in model.D if t in model.DT[d]) # Get the day corresponding to t
day_slots = time_slots_dict[l][list(time_slots_dict[l].keys())[0]][day] # Use section A as a representative to check slots
t_index = day_slots.index(t) # Get index of time slot t
max_offset = max(model.q[l, s] for s in model.LS[l]) # Maximum offset for course l
# Ensure there is room for the offset
if t_index + max_offset < len(day_slots):
valid_indices.append((l, t))
return valid_indices
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# A function to create variable u indices
valid_u_indices = {(p, d, t)
for p in model.P
for d in model.D
for t in model.DT[d] if t in model.PT[p]}
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Variables of the model
# Binary variable x(j,s,t) is 1 if course j, section s, scheduled in time t
model.x = pyo.Var(x_ind_list, domain=pyo.Binary)
# Binary variable y(f,j,t) is 1 if course j has an available section at time t
model.y = pyo.Var(valid_index_y, domain=pyo.Binary)
# Binary variable z(l,t) is 1 if all sections of course l are scheduled back-to-back starting at t
model.z = pyo.Var(valid_index_z, domain=pyo.Binary)
# Binary variable tau(j,s,t) is 1 if course j, section s, not scheduled at desirable time t
model.tau = pyo.Var(x_ind_list, domain=pyo.NonNegativeReals)
# Binary variable pi(j,s,t) is 1 if course j, section s, scheduled at not desirable time t
model.pi = pyo.Var(x_ind_list, domain=pyo.NonNegativeReals)
# Binary variable gamma(j,s,t) is 1 if course j, section s, scheduled at time t,
# which is not a desirable day for the course professor
model.gamma = pyo.Var(x_ind_list, domain=pyo.NonNegativeReals)
# Integer variable eta(f,b) indicates the number of conflicted courses in group f with block b
model.eta = pyo.Var(model.F*model.B, domain=pyo.NonNegativeReals)
# Integer variable mu(p) indicates the number of courses of Professor P
# that do not satisfy their preferred time to teach
model.mu = pyo.Var(model.P, domain=pyo.NonNegativeReals)
# Binary variable alpha(p,d,t) indicates if t is the first class of professor p on day d
model.alpha = pyo.Var(valid_u_indices, within=pyo.Binary)
# Binary variable beta(p,d,t) indicates if t is the last class of professor p on day d
model.beta = pyo.Var(valid_u_indices, within=pyo.Binary)
# Continuous variable w(p,d) indicates the working span of professor p on day d
model.w = pyo.Var(model.P, model.D, within=pyo.NonNegativeReals)
# Continuous variable theta(p,d) indicates the extra working hours of professor p on day d
model.theta = pyo.Var(model.P, model.D, within=pyo.NonNegativeReals)
# Binary variable m(p,d) indicates if professor p teaches on day d
model.m = pyo.Var(model.P, model.D, within=pyo.Binary)
# Continuous variable delta(p,d) indicates the extra working days of professor p each week
model.delta = pyo.Var(model.P, within=pyo.NonNegativeReals)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define the objective function expression
def obj_expression(model):
return (pyo.summation(model.tau)
+ pyo.summation(model.pi)
+ pyo.summation(model.gamma)
+ pyo.summation(model.mu)
+ pyo.summation(model.eta)
+ pyo.summation(model.theta)
+ pyo.summation(model.delta)
)
model.Obj = pyo.Objective(rule=obj_expression, sense=pyo.minimize)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def constraint_rule1(model, f, j, t):
if f in model.F and j in model.FJ[f] and t in model.JT[j]:
return model.y[f,j,t] <= sum(model.x[j,s,t]
for s in model.JS[j]
if (j, s) in model.JST and t in model.JST[j, s])
else:
return pyo.Constraint.Skip
model.Const1 = pyo.Constraint(model.F, model.J, model.T, rule=constraint_rule1)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def constraint_rule2(model, f, j):
if j in model.FJ[f]:
return (sum(sum(model.y[f,j,t]
for t in model.UT[u])
for u in model.JU[j])
== 1)
else:
return pyo.Constraint.Skip
model.Const2 = pyo.Constraint(model.F, model.J, rule=constraint_rule2)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def constraint_rule3(model, f, b):