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}