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