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 Tomas Muller<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
051    public Student(Long studentId) {
052        iStudentId = studentId;
053    }
054
055    public void addOffering(Long offeringId, double weight, Double priority) {
056        iOfferings.put(offeringId, weight);
057        if (priority != null) iOfferingPriority.put(offeringId, priority);
058    }
059    
060    public void addOffering(Long offeringId, double weight) {
061        addOffering(offeringId, weight, null);
062    }
063
064    public Map<Long, Double> getOfferingsMap() {
065        return iOfferings;
066    }
067
068    public Set<Long> getOfferings() {
069        return iOfferings.keySet();
070    }
071
072    public boolean hasOffering(Long offeringId) {
073        return iOfferings.containsKey(offeringId);
074    }
075    
076    public InstructorConstraint getInstructor() { return iInstructor; }
077    
078    public void setInstructor(InstructorConstraint instructor) { iInstructor = instructor; }
079    
080    /**
081     * Priority of an offering (for the student). Null if not used, or between
082     * zero (no priority) and one (highest priority)
083     * @param offeringId instructional offering unique id
084     * @return student's priority
085     */
086    public Double getPriority(Long offeringId) {
087        return offeringId == null ? null : iOfferingPriority.get(offeringId);
088    }
089    
090    public Double getPriority(Configuration configuration) {
091        return configuration == null ? null : getPriority(configuration.getOfferingId());
092    }
093    
094    public Double getPriority(Lecture lecture) {
095        return lecture == null ? null : getPriority(lecture.getConfiguration());
096    }
097    
098    public Double getConflictingPriorty(Lecture l1, Lecture l2) {
099        // Conflicting priority is the lower of the two priorities
100        Double p1 = getPriority(l1);
101        Double p2 = getPriority(l2);
102        return p1 == null ? null : p2 == null ? null : Math.min(p1, p2);
103    }
104
105    public double getOfferingWeight(Configuration configuration) {
106        if (configuration == null)
107            return 1.0;
108        return getOfferingWeight(configuration.getOfferingId());
109    }
110
111    public double getOfferingWeight(Long offeringId) {
112        Double weight = iOfferings.get(offeringId);
113        return (weight == null ? 0.0 : weight.doubleValue());
114    }
115    
116    public boolean canUnenroll(Lecture lecture) {
117        if (getInstructor() != null)
118            return !getInstructor().variables().contains(lecture);
119        return true;
120    }
121
122    public boolean canEnroll(Lecture lecture) {
123        if (iCanNotEnrollSections != null) {
124            Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId());
125            return canEnroll(canNotEnrollLectures, lecture, true);
126        }
127        return true;
128    }
129
130    private boolean canEnroll(Set<Lecture> canNotEnrollLectures, Lecture lecture, boolean checkParents) {
131        if (canNotEnrollLectures == null)
132            return true;
133        if (canNotEnrollLectures.contains(lecture))
134            return false;
135        if (checkParents) {
136            Lecture parent = lecture.getParent();
137            while (parent != null) {
138                if (canNotEnrollLectures.contains(parent))
139                    return false;
140                parent = parent.getParent();
141            }
142        }
143        if (lecture.hasAnyChildren()) {
144            for (Long subpartId: lecture.getChildrenSubpartIds()) {
145                boolean canEnrollChild = false;
146                for (Lecture childLecture : lecture.getChildren(subpartId)) {
147                    if (canEnroll(canNotEnrollLectures, childLecture, false)) {
148                        canEnrollChild = true;
149                        break;
150                    }
151                }
152                if (!canEnrollChild)
153                    return false;
154            }
155        }
156        return true;
157    }
158
159    public void addCanNotEnroll(Lecture lecture) {
160        if (iCanNotEnrollSections == null)
161            iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>();
162        if (lecture.getConfiguration() == null) {
163            sLogger.warn("Student.addCanNotEnroll(" + lecture
164                    + ") -- given lecture has no configuration associated with.");
165            return;
166        }
167        Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(lecture.getConfiguration().getOfferingId());
168        if (canNotEnrollLectures == null) {
169            canNotEnrollLectures = new HashSet<Lecture>();
170            iCanNotEnrollSections.put(lecture.getConfiguration().getOfferingId(), canNotEnrollLectures);
171        }
172        canNotEnrollLectures.add(lecture);
173    }
174
175    public void addCanNotEnroll(Long offeringId, Collection<Lecture> lectures) {
176        if (lectures == null || lectures.isEmpty())
177            return;
178        if (iCanNotEnrollSections == null)
179            iCanNotEnrollSections = new HashMap<Long, Set<Lecture>>();
180        Set<Lecture> canNotEnrollLectures = iCanNotEnrollSections.get(offeringId);
181        if (canNotEnrollLectures == null) {
182            canNotEnrollLectures = new HashSet<Lecture>();
183            iCanNotEnrollSections.put(offeringId, canNotEnrollLectures);
184        }
185        canNotEnrollLectures.addAll(lectures);
186    }
187
188    public Map<Long, Set<Lecture>> canNotEnrollSections() {
189        return iCanNotEnrollSections;
190    }
191
192    public void addLecture(Lecture lecture) {
193        iLectures.add(lecture);
194    }
195
196    public void removeLecture(Lecture lecture) {
197        iLectures.remove(lecture);
198    }
199
200    public Set<Lecture> getLectures() {
201        return iLectures;
202    }
203
204    public void addConfiguration(Configuration config) {
205        if (config != null) iConfigurations.add(config);
206    }
207
208    public void removeConfiguration(Configuration config) {
209        if (config != null) iConfigurations.remove(config);
210    }
211
212    public Set<Configuration> getConfigurations() {
213        return iConfigurations;
214    }
215
216    public Long getId() {
217        return iStudentId;
218    }
219
220    public double getDistance(Student student) {
221        Double dist = (USE_DISTANCE_CACHE && iDistanceCache != null ? iDistanceCache.get(student) : null);
222        if (dist == null) {
223            if (!getGroups().isEmpty() || !student.getGroups().isEmpty()) {
224                double total = 0.0f;
225                double same = 0.0;
226                for (StudentGroup g: getGroups()) {
227                    total += g.getWeight();
228                    if (student.hasGroup(g))
229                        same += g.getWeight();
230                }
231                for (StudentGroup g: student.getGroups()) {
232                    total += g.getWeight();
233                }
234                dist = (total - 2*same) / total;
235            } else {
236                int same = 0;
237                for (Long o : getOfferings()) {
238                    if (student.getOfferings().contains(o))
239                        same++;
240                }
241                double all = student.getOfferings().size() + getOfferings().size();
242                double dif = all - 2.0 * same;
243                dist = new Double(dif / all);
244            }
245            if (USE_DISTANCE_CACHE) {
246                if (iDistanceCache == null)
247                    iDistanceCache = new HashMap<Student, Double>();
248                iDistanceCache.put(student, dist);
249            }
250        }
251        return dist.doubleValue();
252    }
253
254    public void clearDistanceCache() {
255        if (USE_DISTANCE_CACHE && iDistanceCache != null)
256            iDistanceCache.clear();
257    }
258
259    @Override
260    public String toString() {
261        return String.valueOf(getId());
262    }
263
264    @Override
265    public int hashCode() {
266        return getId().hashCode();
267    }
268
269    @Override
270    public int compareTo(Student s) {
271        return getId().compareTo(s.getId());
272    }
273
274    @Override
275    public boolean equals(Object o) {
276        if (o == null || !(o instanceof Student))
277            return false;
278        return getId().equals(((Student) o).getId());
279    }
280
281    public void addCommitedPlacement(Placement placement) {
282        if (iCommitedPlacements == null)
283            iCommitedPlacements = new HashSet<Placement>();
284        iCommitedPlacements.add(placement);
285    }
286
287    public Set<Placement> getCommitedPlacements() {
288        return iCommitedPlacements;
289    }
290
291    public Set<Placement> conflictPlacements(Placement placement) {
292        if (iCommitedPlacements == null)
293            return null;
294        Set<Placement> ret = new HashSet<Placement>();
295        Lecture lecture = placement.variable();
296        for (Placement commitedPlacement : iCommitedPlacements) {
297            Lecture commitedLecture = commitedPlacement.variable();
298            if (lecture.getSchedulingSubpartId() != null
299                    && lecture.getSchedulingSubpartId().equals(commitedLecture.getSchedulingSubpartId()))
300                continue;
301            if (lecture.isToIgnoreStudentConflictsWith(commitedLecture)) continue;
302            if (JenrlConstraint.isInConflict(commitedPlacement, placement, ((TimetableModel)placement.variable().getModel()).getDistanceMetric()))
303                ret.add(commitedPlacement);
304        }
305        return ret;
306    }
307
308    public int countConflictPlacements(Placement placement) {
309        Set<Placement> conflicts = conflictPlacements(placement);
310        double w = getOfferingWeight((placement.variable()).getConfiguration());
311        return (int) Math.round(conflicts == null ? 0 : avg(w, 1.0) * conflicts.size());
312    }
313
314    public double getJenrlWeight(Lecture l1, Lecture l2) {
315        if (getInstructor() != null && (getInstructor().variables().contains(l1) || getInstructor().variables().contains(l2)))
316            return 1.0;
317        return avg(getOfferingWeight(l1.getConfiguration()), getOfferingWeight(l2.getConfiguration()));
318    }
319
320    public double avg(double w1, double w2) {
321        return Math.sqrt(w1 * w2);
322    }
323    
324    public String getAcademicArea() {
325        return iAcademicArea;
326    }
327    
328    public void setAcademicArea(String acadArea) {
329        iAcademicArea = acadArea;
330    }
331    
332    public String getAcademicClassification() {
333        return iAcademicClassification;
334    }
335    
336    public void setAcademicClassification(String acadClasf) {
337        iAcademicClassification = acadClasf;
338    }
339    
340    public String getMajor() {
341        return iMajor;
342    }
343    
344    public void setMajor(String major) {
345        iMajor = major;
346    }
347    
348    public String getCurriculum() {
349        return iCurriculum;
350    }
351    
352    public void setCurriculum(String curriculum) {
353        iCurriculum = curriculum;
354    }
355    
356    public void addGroup(StudentGroup group) {
357        iGroups.add(group);
358    }
359    
360    public Set<StudentGroup> getGroups() { return iGroups; }
361    
362    public boolean hasGroup(StudentGroup group) { return iGroups.contains(group); }
363    
364    public String getGroupNames() {
365        if (iGroups.isEmpty()) return "";
366        if (iGroups.size() == 1) return iGroups.iterator().next().getName();
367        String ret = "";
368        for (StudentGroup g: new TreeSet<StudentGroup>(iGroups))
369            ret += (ret.isEmpty() ? "" : ", ") + g.getName();
370        return ret;
371    }
372    
373    public double getSameGroupWeight(Student other) {
374        double ret = 0.0;
375        for (StudentGroup group: iGroups)
376            if (other.hasGroup(group) && group.getWeight() > ret) ret = group.getWeight();
377        return ret;
378    }
379}