    001package org.cpsolver.coursett.model;
    002
    003import java.util.Collection;
    004import java.util.HashSet;
    005import java.util.HashMap;
    006import java.util.Map;
    007import java.util.Set;
    008import java.util.TreeSet;
    009
    010import org.cpsolver.coursett.constraint.InstructorConstraint;
    011import org.cpsolver.coursett.constraint.JenrlConstraint;
    012
    013
    014/**
    015 * Student.
    016 *
    017 * @author  Tomáš Müller
    018 * @version CourseTT 1.3 (University Course Timetabling)<br>
    019 *          Copyright (C) 2006 - 2014 Tomáš Müller<br>
    020 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    021 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    022 * <br>
    023 *          This library is free software; you can redistribute it and/or modify
    024 *          it under the terms of the GNU Lesser General Public License as
    025 *          published by the Free Software Foundation; either version 3 of the
    026 *          License, or (at your option) any later version. <br>
    027 * <br>
    028 *          This library is distributed in the hope that it will be useful, but
    029 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    030 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    031 *          Lesser General Public License for more details. <br>
    032 * <br>
    033 *          You should have received a copy of the GNU Lesser General Public
    034 *          License along with this library; if not see
    035 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    036 */
    037public class Student implements Comparable<Student> {
    038    private static org.apache.logging.log4j.Logger sLogger = org.apache.logging.log4j.LogManager.getLogger(Student.class);
    039    public static boolean USE_DISTANCE_CACHE = false;
    040    Long iStudentId = null;
    041    HashMap<Long, Double> iOfferings = new HashMap<Long, Double>();
    042    Set<Lecture> iLectures = new HashSet<Lecture>();
    043    Set<Configuration> iConfigurations = new HashSet<Configuration>();
    044    HashMap<Long, Set<Lecture>> iCanNotEnrollSections = null;
    045    HashMap<Student, Double> iDistanceCache = null;
    046    HashSet<Placement> iCommitedPlacements = null;
    047    private String iAcademicArea = null, iAcademicClassification = null, iMajor = null, iCurriculum = null;
    048    HashMap<Long, Double> iOfferingPriority = new HashMap<Long, Double>();
    049    private InstructorConstraint iInstructor = null;
    050    private Set<StudentGroup> iGroups = new HashSet<StudentGroup>();
    051    private Map<Long, Set<Long>> iAlternatives = null;
    052
    053    public Student(Long studentId) {
    054        iStudentId = studentId;
    055    }
    056
    057    public void addOffering(Long offeringId, double weight, Double priority) {
    058        iOfferings.put(offeringId, weight);
    059        if (priority != null) iOfferingPriority.put(offeringId, priority);
    060    }
    061
    062    public void addOffering(Long offeringId, double weight) {
    063        addOffering(offeringId, weight, null);
    064    }
    065
    066    public Map<Long, Double> getOfferingsMap() {
    067        return iOfferings;
    068    }
    069
    070    public Set<Long> getOfferings() {
    071        return iOfferings.keySet();
    072    }
    073
    074    public boolean hasOffering(Long offeringId) {
    075        return iOfferings.containsKey(offeringId);
    076    }
    077
    078    public InstructorConstraint getInstructor() { return iInstructor; }
    079
    080    public void setInstructor(InstructorConstraint instructor) { iInstructor = instructor; }
    081
    082    /**
    083     * Priority of an offering (for the student). Null if not used, or between
    084     * zero (no priority) and one (highest priority)
    085     * @param offeringId instructional offering unique id
    086     * @return student's priority
    087     */
    088    public Double getPriority(Long offeringId) {
    089        return offeringId == null ? null : iOfferingPriority.get(offeringId);
    090    }
    091
    092    public Double getPriority(Configuration configuration) {
    093        return configuration == null ? null : getPriority(configuration.getOfferingId());
    094    }
    095
    096    public Double getPriority(Lecture lecture) {
    097        return lecture == null ? null : getPriority(lecture.getConfiguration());
    098    }
    099
    100    public Double getConflictingPriorty(Lecture l1, Lecture l2) {
    101        // Conflicting priority is the lower of the two priorities
    102        Double p1 = getPriority(l1);
    103        Double p2 = getPriority(l2);
    104        return p1 == null ? null : p2 == null ? null : Math.min(p1, p2);
    105    }
    106
    107    public double getOfferingWeight(Configuration configuration) {
    108        if (configuration == null)
    109            return 1.0;
    110        return getOfferingWeight(configuration.getOfferingId());
    111    }
    112
    113    public double getOfferingWeight(Long offeringId) {
    114        Double weight = iOfferings.get(offeringId);
    115        return (weight == null ? 0.0 : weight.doubleValue());
    116    }
    117
    118    public boolean canUnenroll(Lecture lecture) {
    119        if (getInstructor() != null)
    120            return !getInstructor().variables().contains(lecture);
    121        return true;
    122    }
    123
    124    public boolean canEnroll(Lecture lecture) {
    125        if (iCanNotEnrollSections != null) {
    126            Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId());
    127            return canEnroll(canNotEnrollLectures, lecture, true);
    128        }
    129        return true;
    130    }
    131
    132    public boolean canEnroll(Configuration config) {
    133        for (Long subpartId: config.getTopSubpartIds()) {
    134            boolean canEnrollThisSubpart = false;
    135            for (Lecture lecture : config.getTopLectures(subpartId)) {
    136                if (canEnroll(lecture)) {
    137                    canEnrollThisSubpart = true;
    138                    break;
    139                }
    140            }
    141            if (!canEnrollThisSubpart)
    142                return false;
    143        }
    144        return true;
    145    }
    146
    147    private boolean canEnroll(Set<Lecture> canNotEnrollLectures, Lecture lecture, boolean checkParents) {
    148        if (canNotEnrollLectures == null)
    149            return true;
    150        if (canNotEnrollLectures.contains(lecture))
    151            return false;
    152        if (checkParents) {
    153            Lecture parent = lecture.getParent();
    154            while (parent != null) {
    155                if (canNotEnrollLectures.contains(parent))
    156                    return false;
    157                parent = parent.getParent();
    158            }
    159        }
    160        if (lecture.hasAnyChildren()) {
    161            for (Long subpartId: lecture.getChildrenSubpartIds()) {
    162                boolean canEnrollChild = false;
    163                for (Lecture childLecture : lecture.getChildren(subpartId)) {
    164                    if (canEnroll(canNotEnrollLectures, childLecture, false)) {
    165                        canEnrollChild = true;
    166                        break;
    167                    }
    168                }
    169                if (!canEnrollChild)
    170                    return false;
    171            }
    172        }
    173        return true;
    174    }
    175
    176    public void addCanNotEnroll(Lecture lecture) {
    177        if (iCanNotEnrollSections == null)
    178            iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>();
    179        if (lecture.getConfiguration() == null) {
    180            sLogger.warn("Student.addCanNotEnroll(" + lecture
    181                    + ") -- given lecture has no configuration associated with.");
    182            return;
    183        }
    184        Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId());
    185        if (canNotEnrollLectures == null) {
    186            canNotEnrollLectures = new HashSet<Lecture>();
    187            iCanNotEnrollSections.put(lecture.getConfiguration().getOfferingId(), canNotEnrollLectures);
    188        }
    189        canNotEnrollLectures.add(lecture);
    190    }
    191
    192    public void addCanNotEnroll(Long offeringId, Collection<Lecture> lectures) {
    193        if (lectures == null || lectures.isEmpty())
    194            return;
    195        if (iCanNotEnrollSections == null)
    196            iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>();
    197        Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(offeringId);
    198        if (canNotEnrollLectures == null) {
    199            canNotEnrollLectures = new HashSet<Lecture>();
    200            iCanNotEnrollSections.put(offeringId, canNotEnrollLectures);
    201        }
    202        canNotEnrollLectures.addAll(lectures);
    203    }
    204
    205    public Map<Long, Set<Lecture>> canNotEnrollSections() {
    206        return iCanNotEnrollSections;
    207    }
    208
    209    public void addLecture(Lecture lecture) {
    210        iLectures.add(lecture);
    211    }
    212
    213    public void removeLecture(Lecture lecture) {
    214        iLectures.remove(lecture);
    215    }
    216
    217    public Set<Lecture> getLectures() {
    218        return iLectures;
    219    }
    220
    221    public void addConfiguration(Configuration config) {
    222        if (config != null) iConfigurations.add(config);
    223    }
    224
    225    public void removeConfiguration(Configuration config) {
    226        if (config != null) iConfigurations.remove(config);
    227    }
    228
    229    public Set<Configuration> getConfigurations() {
    230        return iConfigurations;
    231    }
    232
    233    public Long getId() {
    234        return iStudentId;
    235    }
    236
    237    public double getDistance(Student student) {
    238        Double dist = (USE_DISTANCE_CACHE && iDistanceCache != null ? iDistanceCache.get(student) : null);
    239        if (dist == null) {
    240            if (!getGroups().isEmpty() || !student.getGroups().isEmpty()) {
    241                double total = 0.0f;
    242                double same = 0.0;
    243                for (StudentGroup g: getGroups()) {
    244                    total += g.getWeight();
    245                    if (student.hasGroup(g))
    246                        same += g.getWeight();
    247                }
    248                for (StudentGroup g: student.getGroups()) {
    249                    total += g.getWeight();
    250                }
    251                dist = (total - 2*same) / total;
    252            } else {
    253                int same = 0;
    254                for (Long o : getOfferings()) {
    255                    if (student.getOfferings().contains(o))
    256                        same++;
    257                }
    258                double all = student.getOfferings().size() + getOfferings().size();
    259                double dif = all - 2.0 * same;
    260                dist = Double.valueOf(dif / all);
    261            }
    262            if (USE_DISTANCE_CACHE) {
    263                if (iDistanceCache == null)
    264                    iDistanceCache = new HashMap<Student, Double>();
    265                iDistanceCache.put(student, dist);
    266            }
    267        }
    268        return dist.doubleValue();
    269    }
    270
    271    public void clearDistanceCache() {
    272        if (USE_DISTANCE_CACHE && iDistanceCache != null)
    273            iDistanceCache.clear();
    274    }
    275
    276    @Override
    277    public String toString() {
    278        return String.valueOf(getId());
    279    }
    280
    281    @Override
    282    public int hashCode() {
    283        return getId().hashCode();
    284    }
    285
    286    @Override
    287    public int compareTo(Student s) {
    288        return getId().compareTo(s.getId());
    289    }
    290
    291    @Override
    292    public boolean equals(Object o) {
    293        if (o == null || !(o instanceof Student))
    294            return false;
    295        return getId().equals(((Student) o).getId());
    296    }
    297
    298    public void addCommitedPlacement(Placement placement) {
    299        if (iCommitedPlacements == null)
    300            iCommitedPlacements = new HashSet<Placement>();
    301        iCommitedPlacements.add(placement);
    302    }
    303
    304    public Set<Placement> getCommitedPlacements() {
    305        return iCommitedPlacements;
    306    }
    307
    308    public Set<Placement> conflictPlacements(Placement placement) {
    309        if (iCommitedPlacements == null)
    310            return null;
    311        Set<Placement> ret = new HashSet<Placement>();
    312        Lecture lecture = placement.variable();
    313        for (Placement commitedPlacement : iCommitedPlacements) {
    314            Lecture commitedLecture = commitedPlacement.variable();
    315            if (lecture.getSchedulingSubpartId() != null
    316                    && lecture.getSchedulingSubpartId().equals(commitedLecture.getSchedulingSubpartId()))
    317                continue;
    318            if (lecture.isToIgnoreStudentConflictsWith(commitedLecture)) continue;
    319            if (JenrlConstraint.isInConflict(commitedPlacement, placement, ((TimetableModel)placement.variable().getModel()).getDistanceMetric(),
    320                    ((TimetableModel)placement.variable().getModel()).getStudentWorkDayLimit()))
    321                ret.add(commitedPlacement);
    322        }
    323        return ret;
    324    }
    325
    326    public int countConflictPlacements(Placement placement) {
    327        Set<Placement> conflicts = conflictPlacements(placement);
    328        double w = getOfferingWeight((placement.variable()).getConfiguration());
    329        return (int) Math.round(conflicts == null ? 0 : avg(w, 1.0) * conflicts.size());
    330    }
    331
    332    public double getJenrlWeight(Lecture l1, Lecture l2) {
    333        if (getInstructor() != null && (getInstructor().variables().contains(l1) || getInstructor().variables().contains(l2)))
    334            return 1.0;
    335        if (iAlternatives != null && areAlternatives(l1, l2)) return 0.0;
    336        return avg(getOfferingWeight(l1.getConfiguration()), getOfferingWeight(l2.getConfiguration()));
    337    }
    338
    339    public void addAlternatives(Long offeringId1, Long offeringId2) {
    340        if (offeringId1 == null || offeringId2 == null) return;
    341        if (iAlternatives == null)
    342            iAlternatives = new HashMap<Long, Set<Long>>();
    343        Set<Long> alts = iAlternatives.get(offeringId1);
    344        if (alts == null) {
    345            alts = new HashSet<Long>();
    346            alts.add(offeringId1);
    347            iAlternatives.put(offeringId1, alts);
    348        }
    349        Set<Long> other = iAlternatives.get(offeringId2);
    350        if (other != null) {
    351            for (Long id: other)
    352                iAlternatives.put(id, alts);
    353            alts.addAll(other);
    354        } else {
    355            alts.add(offeringId2);
    356            iAlternatives.put(offeringId2, alts);
    357        }
    358    }
    359
    360    public boolean areAlternatives(Lecture l1, Lecture l2) {
    361        if (l1 == null || l2 == null) return false;
    362        return areAlternatives(l1.getConfiguration(), l2.getConfiguration());
    363    }
    364
    365    public boolean areAlternatives(Configuration c1, Configuration c2) {
    366        if (c1 == null || c2 == null) return false;
    367        return areAlternatives(c1.getOfferingId(), c2.getOfferingId());
    368    }
    369
    370    public boolean areAlternatives(Long offeringId1, Long offeringId2) {
    371        if (iAlternatives == null || offeringId1 == null || offeringId2 == null) return false;
    372        Set<Long> alts = iAlternatives.get(offeringId1);
    373        if (alts != null && alts.contains(offeringId2)) return true;
    374        return false;
    375    }
    376
    377    public Long getAlternative(Long offeringId) {
    378        if (iAlternatives == null || offeringId == null) return null;
    379        Set<Long> alternatives = iAlternatives.get(offeringId);
    380        if (alternatives == null) return null;
    381        Long bestId = null; double bestW = 0.0;
    382        for (Long altId: alternatives) {
    383            double w = getOfferingWeight(altId);
    384            if (bestId == null || w > bestW || (bestW == w && altId < bestId)) {
    385                bestId = altId; bestW = w;
    386            }
    387        }
    388        return bestId == null || bestId.equals(offeringId) ? null : bestId;
    389    }
    390
    391    public double avg(double w1, double w2) {
    392        return Math.sqrt(w1 * w2);
    393    }
    394
    395    public String getAcademicArea() {
    396        return iAcademicArea;
    397    }
    398
    399    public void setAcademicArea(String acadArea) {
    400        iAcademicArea = acadArea;
    401    }
    402
    403    public String getAcademicClassification() {
    404        return iAcademicClassification;
    405    }
    406
    407    public void setAcademicClassification(String acadClasf) {
    408        iAcademicClassification = acadClasf;
    409    }
    410
    411    public String getMajor() {
    412        return iMajor;
    413    }
    414
    415    public void setMajor(String major) {
    416        iMajor = major;
    417    }
    418
    419    public String getCurriculum() {
    420        return iCurriculum;
    421    }
    422
    423    public void setCurriculum(String curriculum) {
    424        iCurriculum = curriculum;
    425    }
    426
    427    public void addGroup(StudentGroup group) {
    428        iGroups.add(group);
    429    }
    430
    431    public Set<StudentGroup> getGroups() { return iGroups; }
    432
    433    public boolean hasGroup(StudentGroup group) { return iGroups.contains(group); }
    434
    435    public String getGroupNames() {
    436        if (iGroups.isEmpty()) return "";
    437        if (iGroups.size() == 1) return iGroups.iterator().next().getName();
    438        String ret = "";
    439        for (StudentGroup g: new TreeSet<StudentGroup>(iGroups))
    440            ret += (ret.isEmpty() ? "" : ", ") + g.getName();
    441        return ret;
    442    }
    443
    444    public double getSameGroupWeight(Student other) {
    445        double ret = 0.0;
    446        for (StudentGroup group: iGroups)
    447            if (other.hasGroup(group) && group.getWeight() > ret) ret = group.getWeight();
    448        return ret;
    449    }
    450}