    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}