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}