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