001package org.cpsolver.instructor.model; 002 003import java.util.Collection; 004 005import org.cpsolver.coursett.Constants; 006import org.cpsolver.coursett.model.TimeLocation; 007import org.cpsolver.instructor.criteria.DifferentLecture; 008 009/** 010 * Section. A section (part of a teaching request that needs an instructor) has an id, a name, a time, a room. 011 * A section may be allowed to overlap in time (in which case the overlapping time is to be minimized) and/or 012 * marked as common. This is, for instance, to be able to ensure that all assignments of a course that are 013 * given to a single instructor share the same lecture. 014 * 015 * @author Tomáš Müller 016 * @version IFS 1.3 (Instructor Sectioning)<br> 017 * Copyright (C) 2016 Tomáš Müller<br> 018 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 019 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 020 * <br> 021 * This library is free software; you can redistribute it and/or modify 022 * it under the terms of the GNU Lesser General Public License as 023 * published by the Free Software Foundation; either version 3 of the 024 * License, or (at your option) any later version. <br> 025 * <br> 026 * This library is distributed in the hope that it will be useful, but 027 * WITHOUT ANY WARRANTY; without even the implied warranty of 028 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 029 * Lesser General Public License for more details. <br> 030 * <br> 031 * You should have received a copy of the GNU Lesser General Public 032 * License along with this library; if not see 033 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 034 */ 035public class Section { 036 private Long iId; 037 private String iExternalId; 038 private String iType; 039 private String iName; 040 private TimeLocation iTime; 041 private String iRoom; 042 private boolean iAllowOverlap; 043 private boolean iCommon; 044 045 /** 046 * Constructor 047 * @param id section unique id 048 * @param externalId section external id 049 * @param type instructional type 050 * @param name section name 051 * @param time section time assignment 052 * @param room section room assignment 053 * @param allowOverlap can this section overlap with some other section 054 * @param common is this a common part of the course 055 */ 056 public Section(long id, String externalId, String type, String name, TimeLocation time, String room, boolean allowOverlap, boolean common) { 057 iId = id; 058 iExternalId = externalId; 059 iType = type; 060 iName = name; 061 iTime = time; 062 iRoom = room; 063 iAllowOverlap = allowOverlap; 064 iCommon = common; 065 } 066 067 /** 068 * Section unique id that was provided in the constructor 069 * @return section unique id 070 */ 071 public Long getSectionId() { return iId; } 072 073 /** 074 * Section external unique id that was provided in the constructor 075 * @return section external id 076 */ 077 public String getExternalId() { return iExternalId; } 078 079 /** 080 * Section instructional type (e.g., Lecture) that was provided in the constructor 081 * @return section instructional type 082 */ 083 public String getSectionType() { return iType; } 084 085 /** 086 * Has section type filled in? 087 * @return true, if there is a section instructional type filled in 088 */ 089 public boolean hasSectionType() { return iType != null && !iType.isEmpty(); } 090 091 /** 092 * Section name that was provided in the constructor 093 * @return section name 094 */ 095 public String getSectionName() { return iName; } 096 097 /** 098 * Section time that was provided in the constructor 099 * @return section time 100 */ 101 public TimeLocation getTime() { return iTime; } 102 103 /** 104 * Has section time filled in? 105 * @return true if section is assigned in time 106 */ 107 public boolean hasTime() { return getTime() != null && getTime().getDayCode() != 0; } 108 109 /** 110 * Section room (or rooms) 111 * @return section room 112 */ 113 public String getRoom() { return iRoom; } 114 115 /** 116 * Has section room filled in? 117 * @return true if section is assigned in space 118 */ 119 public boolean hasRoom() { return iRoom != null && !iRoom.isEmpty(); } 120 121 /** 122 * Are time overlaps with other sections and with prohibited time preferences allowed? If true, the number of overlapping time slots should be minimized instead. 123 * @return true if other sections can overlap with this section or if the student can teach this section even when he/she is unavailable 124 */ 125 public boolean isAllowOverlap() { return iAllowOverlap; } 126 127 /** 128 * Is common part of the course (e.g., a lecture)? It is possible to either require all assignments of a course that are given to the same instructor to have 129 * to share the common part (when {@link TeachingRequest#getSameCommonPreference()} is true) or to minimize the different sections that are not shared among all the assignments 130 * (using {@link DifferentLecture} criterion and {@link TeachingRequest#nrSameLectures(TeachingRequest)}). 131 * @return true if this section forms a common part of the course 132 */ 133 public boolean isCommon() { return iCommon; } 134 135 /** 136 * Check if this section overlaps in time with some other section 137 * @param section the other section 138 * @return true, if neither of the two sections allow for overlap and they are assigned in overlapping times (see {@link TimeLocation#hasIntersection(TimeLocation)}) 139 */ 140 public boolean isOverlapping(Section section) { 141 if (isAllowOverlap() || section.isAllowOverlap()) return false; 142 if (getTime() == null || section.getTime() == null) return false; 143 return getTime().hasIntersection(section.getTime()); 144 } 145 146 /** 147 * Check if this section overlaps in time with at least one of the given sections 148 * @param sections the other sections 149 * @return true, if there is a section among the sections that overlaps in time with this section (see {@link Section#isOverlapping(Section)}) 150 */ 151 public boolean isOverlapping(Collection<Section> sections) { 152 if (isAllowOverlap()) return false; 153 if (getTime() == null) return false; 154 if (sections.contains(this)) return false; 155 for (Section section : sections) { 156 if (section.isAllowOverlap()) continue; 157 if (section.getTime() == null) continue; 158 if (getTime().hasIntersection(section.getTime())) return true; 159 } 160 return false; 161 } 162 163 /** 164 * Check if this section is back to back with some other section 165 * @param section the other section 166 * @return true, if this section is back-to-back with the other section 167 */ 168 public boolean isBackToBack(Section section) { 169 if (getTime() == null || section.getTime() == null) return false; 170 return getTime().shareWeeks(section.getTime()) && getTime().shareDays(section.getTime()) && 171 (getTime().getStartSlot() + getTime().getLength() == section.getTime().getStartSlot() || section.getTime().getStartSlot() + section.getTime().getLength() == getTime().getStartSlot()); 172 } 173 174 /** 175 * Check if this section has the same days as some other section 176 * @param section the other section 177 * @return 0 if all the days are different, 1 if all the days are the same 178 */ 179 public double percSameDays(Section section) { 180 if (getTime() == null || section.getTime() == null) return 0; 181 double ret = 0.0; 182 for (int dayCode: Constants.DAY_CODES) 183 if ((getTime().getDayCode() & dayCode) != 0 && (section.getTime().getDayCode() & dayCode) != 0) ret ++; 184 return ret / Math.min(getTime().getNrMeetings(), section.getTime().getNrMeetings()); 185 } 186 187 /** 188 * Check if this section is placed in the same room as the other section 189 * @param section the other section 190 * @return true, if both sections have no room or if they have the same room 191 */ 192 public boolean isSameRoom(Section section) { 193 return hasRoom() == section.hasRoom() && (!hasRoom() || getRoom().equals(section.getRoom())); 194 } 195 196 /** 197 * Check if this section has the same instructional type as the other section 198 * @param section the other section 199 * @return true, if both sections have no instructional type or if they have the same instructional type 200 */ 201 public boolean isSameSectionType(Section section) { 202 return hasSectionType() == section.hasSectionType() && (!hasSectionType() || getSectionType().equals(section.getSectionType())); 203 } 204 205 /** 206 * Check if this section is back-to-back with some other section in the list 207 * @param sections the other sections 208 * @param diffRoomWeight different room penalty (should be between 1 and 0) 209 * @param diffTypeWeight different instructional type penalty (should be between 1 and 0) 210 * @return 1.0 if there is a section in the list that is back-to-back and in the same room and with the same type, 0.0 if there is no back-to-back section in the list, etc. 211 * If there are multiple back-to-back sections, the best back-to-back value is returned. 212 */ 213 public double countBackToBacks(Collection<Section> sections, double diffRoomWeight, double diffTypeWeight) { 214 if (sections.contains(this)) return 0.0; 215 double btb = 0; 216 for (Section section : sections) 217 if (isBackToBack(section)) { 218 double w = 1.0; 219 if (!isSameRoom(section)) w *= diffRoomWeight; 220 if (!isSameSectionType(section)) w *= diffTypeWeight; 221 if (w > btb) btb = w; 222 } 223 return btb; 224 } 225 226 /** 227 * Check if this section has the same days with some other section in the list 228 * @param sections the other sections 229 * @param diffRoomWeight different room penalty (should be between 1 and 0) 230 * @param diffTypeWeight different instructional type penalty (should be between 1 and 0) 231 * @return 1.0 if there is a section in the list that has the same days and in the same room and with the same type, 0.0 if there is no same days section in the list, etc. 232 * If there are multiple same days sections, the best same days value is returned. 233 */ 234 public double countSameDays(Collection<Section> sections, double diffRoomWeight, double diffTypeWeight) { 235 if (sections.contains(this)) return 0.0; 236 double sd = 0; 237 for (Section section : sections) { 238 double w = percSameDays(section); 239 if (!isSameRoom(section)) w *= diffRoomWeight; 240 if (!isSameSectionType(section)) w *= diffTypeWeight; 241 if (w > sd) sd = w; 242 } 243 return sd; 244 } 245 246 /** 247 * Check if this section has the same room with some other section in the list 248 * @param sections the other sections 249 * @param diffTypeWeight different instructional type penalty (should be between 1 and 0) 250 * @return 1.0 if there is a section in the list that has the same room and with the same type, 0.0 if there is no same room section in the list, etc. 251 * If there are multiple same room sections, the best same room value is returned. 252 */ 253 public double countSameRooms(Collection<Section> sections, double diffTypeWeight) { 254 if (sections.contains(this)) return 0.0; 255 double sr = 0; 256 for (Section section : sections) { 257 if (isSameRoom(section)) { 258 double w = 1.0; 259 if (!isSameSectionType(section)) w *= diffTypeWeight; 260 if (w > sr) sr = w; 261 } 262 } 263 return sr; 264 } 265 266 /** 267 * If this section can overlap in time with the other section, compute the number of overlapping time slots 268 * @param section the other section 269 * @return number of shared days times number of shared slots (a day) 270 */ 271 public int share(Section section) { 272 if (getTime() != null && section.getTime() != null && (isAllowOverlap() || section.isAllowOverlap()) && getTime().hasIntersection(section.getTime())) 273 return getTime().nrSharedDays(section.getTime()) * getTime().nrSharedHours(section.getTime()); 274 else 275 return 0; 276 } 277 278 /** 279 * Compute the number of overlapping time slots between this section and the given time 280 * @param time the given time 281 * @return number of shared days times number of shared slots (a day) 282 */ 283 public int share(TimeLocation time) { 284 if (getTime() != null && time != null && getTime().hasIntersection(time)) 285 return getTime().nrSharedDays(time) * getTime().nrSharedHours(time); 286 else 287 return 0; 288 } 289 290 /** 291 * If this section can overlap in time with any of the given section, compute the number of overlapping time slots 292 * @param sections the other sections 293 * @return number of shared days times number of shared slots (a day) 294 */ 295 public int share(Collection<Section> sections) { 296 if (sections.contains(this)) return 0; 297 int ret = 0; 298 for (Section section : sections) 299 ret += share(section); 300 return ret; 301 } 302 303 /** 304 * Format the time statement 305 * @param useAmPm use 12-hours or 24-hours format 306 * @return <Days of week> <Start time> - <End time> 307 */ 308 public String getTimeName(boolean useAmPm) { 309 if (getTime() == null || getTime().getDayCode() == 0) return "-"; 310 return getTime().getDayHeader() + " " + getTime().getStartTimeHeader(useAmPm) + " - " + getTime().getEndTimeHeader(useAmPm); 311 } 312 313 @Override 314 public String toString() { 315 return (getExternalId() != null ? (getSectionType() == null ? "" : getSectionType() + " ") + getExternalId() : 316 getSectionName() != null ? getSectionName() : getTime() != null ? getTime().getName(true) : "S" + getSectionId()); 317 } 318 319 @Override 320 public int hashCode() { 321 return getSectionId().hashCode(); 322 } 323 324 @Override 325 public boolean equals(Object o) { 326 if (o == null || !(o instanceof Section)) return false; 327 Section s = (Section)o; 328 return getSectionId().equals(s.getSectionId()); 329 } 330}