001package org.cpsolver.instructor.model;
002
003import java.text.DecimalFormat;
004import java.util.ArrayList;
005import java.util.BitSet;
006import java.util.Collection;
007import java.util.Date;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.Iterator;
011import java.util.List;
012import java.util.Map;
013import java.util.Set;
014import java.util.TreeSet;
015
016import org.apache.logging.log4j.Logger;
017import org.cpsolver.coursett.Constants;
018import org.cpsolver.coursett.model.TimeLocation;
019import org.cpsolver.ifs.assignment.Assignment;
020import org.cpsolver.ifs.criteria.Criterion;
021import org.cpsolver.ifs.model.Constraint;
022import org.cpsolver.ifs.model.Model;
023import org.cpsolver.ifs.util.DataProperties;
024import org.cpsolver.ifs.util.ToolBox;
025import org.cpsolver.instructor.constraints.GroupConstraint;
026import org.cpsolver.instructor.constraints.GroupConstraint.Distribution;
027import org.cpsolver.instructor.constraints.InstructorConstraint;
028import org.cpsolver.instructor.constraints.SameInstructorConstraint;
029import org.cpsolver.instructor.constraints.SameLinkConstraint;
030import org.cpsolver.instructor.criteria.UnusedInstructorLoad;
031import org.cpsolver.instructor.criteria.AttributePreferences;
032import org.cpsolver.instructor.criteria.BackToBack;
033import org.cpsolver.instructor.criteria.CoursePreferences;
034import org.cpsolver.instructor.criteria.InstructorPreferences;
035import org.cpsolver.instructor.criteria.SameInstructor;
036import org.cpsolver.instructor.criteria.DifferentLecture;
037import org.cpsolver.instructor.criteria.Distributions;
038import org.cpsolver.instructor.criteria.OriginalInstructor;
039import org.cpsolver.instructor.criteria.SameCommon;
040import org.cpsolver.instructor.criteria.SameCourse;
041import org.cpsolver.instructor.criteria.SameDays;
042import org.cpsolver.instructor.criteria.SameLink;
043import org.cpsolver.instructor.criteria.SameRoom;
044import org.cpsolver.instructor.criteria.TeachingPreferences;
045import org.cpsolver.instructor.criteria.TimeOverlaps;
046import org.cpsolver.instructor.criteria.TimePreferences;
047import org.dom4j.Document;
048import org.dom4j.DocumentHelper;
049import org.dom4j.Element;
050
051/**
052 * Instructor Scheduling Model. Variables are {@link org.cpsolver.instructor.model.TeachingRequest}, values are {@link org.cpsolver.instructor.model.TeachingAssignment}.
053 * Each teaching request has a course (see {@link org.cpsolver.instructor.model.Course}) and one or more sections (see {link {@link org.cpsolver.instructor.model.Section}}).
054 * Each assignment assigns one instructor (see {@link org.cpsolver.instructor.model.Instructor}) to a single teaching request.
055 * 
056 * @author  Tomáš Müller
057 * @version IFS 1.3 (Instructor Sectioning)<br>
058 *          Copyright (C) 2016 Tomáš Müller<br>
059 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
060 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
061 * <br>
062 *          This library is free software; you can redistribute it and/or modify
063 *          it under the terms of the GNU Lesser General Public License as
064 *          published by the Free Software Foundation; either version 3 of the
065 *          License, or (at your option) any later version. <br>
066 * <br>
067 *          This library is distributed in the hope that it will be useful, but
068 *          WITHOUT ANY WARRANTY; without even the implied warranty of
069 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
070 *          Lesser General Public License for more details. <br>
071 * <br>
072 *          You should have received a copy of the GNU Lesser General Public
073 *          License along with this library; if not see
074 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
075 */
076public class InstructorSchedulingModel extends Model<TeachingRequest.Variable, TeachingAssignment> {
077    private static Logger sLog = org.apache.logging.log4j.LogManager.getLogger(InstructorSchedulingModel.class);
078    private DataProperties iProperties;
079    private Set<Attribute.Type> iTypes = new HashSet<Attribute.Type>();
080    private List<Instructor> iInstructors = new ArrayList<Instructor>();
081    private List<TeachingRequest> iRequests = new ArrayList<TeachingRequest>();
082
083    /**
084     * Constructor
085     * @param properties data properties
086     */
087    public InstructorSchedulingModel(DataProperties properties) {
088        super();
089        iProperties = properties;
090        addCriterion(new AttributePreferences());
091        addCriterion(new InstructorPreferences());
092        addCriterion(new TeachingPreferences());
093        addCriterion(new TimePreferences());
094        addCriterion(new CoursePreferences());
095        addCriterion(new BackToBack());
096        addCriterion(new SameInstructor());
097        addCriterion(new TimeOverlaps());
098        addCriterion(new DifferentLecture());
099        addCriterion(new SameLink());
100        addCriterion(new OriginalInstructor());
101        addCriterion(new UnusedInstructorLoad());
102        addCriterion(new SameCourse());
103        addCriterion(new SameCommon());
104        addCriterion(new SameDays());
105        addCriterion(new SameRoom());
106        addCriterion(new Distributions());
107        addGlobalConstraint(new InstructorConstraint());
108        addGlobalConstraint(new GroupConstraint());
109    }
110    
111    /**
112     * Return solver configuration
113     * @return data properties given in the constructor
114     */
115    public DataProperties getProperties() {
116        return iProperties;
117    }
118    
119    /**
120     * Add instructor
121     * @param instructor an instructor
122     */
123    public void addInstructor(Instructor instructor) {
124        instructor.setModel(this);
125        iInstructors.add(instructor);
126        for (Attribute attribute: instructor.getAttributes())
127            addAttributeType(attribute.getType());
128    }
129    
130    /**
131     * All instructors
132     * @return all instructors in the model
133     */
134    public List<Instructor> getInstructors() {
135        return iInstructors;
136    }
137
138    /**
139     * Add teaching request and the related variables
140     * @param request teaching request
141     */
142    public void addRequest(TeachingRequest request) {
143        iRequests.add(request);
144        for (TeachingRequest.Variable variable: request.getVariables())
145            addVariable(variable);
146    }
147    
148    /**
149     * All teaching requests
150     * @return all teaching requests in the model
151     */
152    public List<TeachingRequest> getRequests() {
153        return iRequests;
154    }
155
156    
157    /**
158     * Return registered attribute types
159     * @return attribute types in the problem
160     */
161    public Set<Attribute.Type> getAttributeTypes() { return iTypes; }
162    
163    /**
164     * Register an attribute type
165     * @param type attribute type
166     */
167    public void addAttributeType(Attribute.Type type) { iTypes.add(type); }
168
169    @Override
170    public Map<String, String> getInfo(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
171        Map<String, String> info = super.getInfo(assignment);
172
173        double totalLoad = 0.0;
174        double assignedLoad = 0.0;
175        for (TeachingRequest.Variable clazz : variables()) {
176            totalLoad += clazz.getRequest().getLoad();
177            if (assignment.getValue(clazz) != null)
178                assignedLoad += clazz.getRequest().getLoad();
179        }
180        info.put("Assigned Load", getPerc(assignedLoad, totalLoad, 0) + "% (" + sDoubleFormat.format(assignedLoad) + " / " + sDoubleFormat.format(totalLoad) + ")");
181
182        return info;
183    }
184
185    @Override
186    public double getTotalValue(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
187        double ret = 0;
188        for (Criterion<TeachingRequest.Variable, TeachingAssignment> criterion : getCriteria())
189            ret += criterion.getWeightedValue(assignment);
190        return ret;
191    }
192
193    @Override
194    public double getTotalValue(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment, Collection<TeachingRequest.Variable> variables) {
195        double ret = 0;
196        for (Criterion<TeachingRequest.Variable, TeachingAssignment> criterion : getCriteria())
197            ret += criterion.getWeightedValue(assignment, variables);
198        return ret;
199    }
200    
201    /**
202     * Store the problem (together with its solution) in an XML format
203     * @param assignment current assignment
204     * @return XML document with the problem
205     */
206    public Document save(Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
207        DecimalFormat sDF7 = new DecimalFormat("0000000");
208        boolean saveInitial = getProperties().getPropertyBoolean("Xml.SaveInitial", false);
209        boolean saveBest = getProperties().getPropertyBoolean("Xml.SaveBest", false);
210        boolean saveSolution = getProperties().getPropertyBoolean("Xml.SaveSolution", true);
211        Document document = DocumentHelper.createDocument();
212        if (assignment != null && assignment.nrAssignedVariables() > 0) {
213            StringBuffer comments = new StringBuffer("Solution Info:\n");
214            Map<String, String> solutionInfo = (getProperties().getPropertyBoolean("Xml.ExtendedInfo", true) ? getExtendedInfo(assignment) : getInfo(assignment));
215            for (String key : new TreeSet<String>(solutionInfo.keySet())) {
216                String value = solutionInfo.get(key);
217                comments.append("    " + key + ": " + value + "\n");
218            }
219            document.addComment(comments.toString());
220        }
221        Element root = document.addElement("instructor-schedule");
222        root.addAttribute("version", "1.0");
223        root.addAttribute("created", String.valueOf(new Date()));
224        Element typesEl = root.addElement("attributes");
225        for (Attribute.Type type: getAttributeTypes()) {
226            Element typeEl = typesEl.addElement("type");
227            if (type.getTypeId() != null)
228                typeEl.addAttribute("id", String.valueOf(type.getTypeId()));
229            typeEl.addAttribute("name", type.getTypeName());
230            typeEl.addAttribute("conjunctive", type.isConjunctive() ? "true" : "false");
231            typeEl.addAttribute("required", type.isRequired() ? "true": "false");
232            Set<Attribute> attributes = new HashSet<Attribute>();
233            for (TeachingRequest request: getRequests()) {
234                for (Preference<Attribute> pref: request.getAttributePreferences()) {
235                    Attribute attribute = pref.getTarget();
236                    if (type.equals(attribute.getType()) && attributes.add(attribute)) {
237                        Element attributeEl = typeEl.addElement("attribute");
238                        if (attribute.getAttributeId() != null)
239                            attributeEl.addAttribute("id", String.valueOf(attribute.getAttributeId()));
240                        attributeEl.addAttribute("name", attribute.getAttributeName());
241                        if (attribute.getParentAttribute() != null && attribute.getParentAttribute().getAttributeId() != null)
242                            attributeEl.addAttribute("parent", String.valueOf(attribute.getParentAttribute().getAttributeId()));
243                    }
244                }
245                for (Instructor instructor: getInstructors()) {
246                    for (Attribute attribute: instructor.getAttributes()) {
247                        if (type.equals(attribute.getType()) && attributes.add(attribute)) {
248                            Element attributeEl = typeEl.addElement("attribute");
249                            if (attribute.getAttributeId() != null)
250                                attributeEl.addAttribute("id", String.valueOf(attribute.getAttributeId()));
251                            attributeEl.addAttribute("name", attribute.getAttributeName());
252                            if (attribute.getParentAttribute() != null && attribute.getParentAttribute().getAttributeId() != null)
253                                attributeEl.addAttribute("parent", String.valueOf(attribute.getParentAttribute().getAttributeId()));
254                        }
255                    }
256                }
257            }
258        }
259        Element requestsEl = root.addElement("teaching-requests");
260        for (TeachingRequest request: getRequests()) {
261            Element requestEl = requestsEl.addElement("request");
262            requestEl.addAttribute("id", String.valueOf(request.getRequestId()));
263            if (request.getNrInstructors() != 1)
264                requestEl.addAttribute("nrInstructors", String.valueOf(request.getNrInstructors()));
265            Course course = request.getCourse();
266            Element courseEl = requestEl.addElement("course");
267            if (course.getCourseId() != null)
268                courseEl.addAttribute("id", String.valueOf(course.getCourseId()));
269            if (course.getCourseName() != null)
270                courseEl.addAttribute("name", String.valueOf(course.getCourseName()));
271            for (Section section: request.getSections()) {
272                Element sectionEl = requestEl.addElement("section");
273                sectionEl.addAttribute("id", String.valueOf(section.getSectionId()));
274                if (section.getExternalId() != null && !section.getExternalId().isEmpty()) sectionEl.addAttribute("externalId", section.getExternalId());
275                if (section.getSectionType() != null && !section.getSectionType().isEmpty()) sectionEl.addAttribute("type", section.getSectionType());
276                if (section.getSectionName() != null && !section.getSectionName().isEmpty()) sectionEl.addAttribute("name", section.getSectionName());
277                if (section.hasTime()) {
278                    TimeLocation tl = section.getTime();
279                    Element timeEl = sectionEl.addElement("time");
280                    timeEl.addAttribute("days", sDF7.format(Long.parseLong(Integer.toBinaryString(tl.getDayCode()))));
281                    timeEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
282                    timeEl.addAttribute("length", String.valueOf(tl.getLength()));
283                    if (tl.getBreakTime() != 0)
284                        timeEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime()));
285                    if (tl.getTimePatternId() != null)
286                        timeEl.addAttribute("pattern", tl.getTimePatternId().toString());
287                    if (tl.getDatePatternId() != null)
288                        timeEl.addAttribute("datePattern", tl.getDatePatternId().toString());
289                    if (tl.getDatePatternName() != null && !tl.getDatePatternName().isEmpty())
290                        timeEl.addAttribute("datePatternName", tl.getDatePatternName());
291                    if (tl.getWeekCode() != null)
292                        timeEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
293                    timeEl.setText(tl.getLongName(false));
294                }
295                if (section.hasRoom()) sectionEl.addAttribute("room", section.getRoom());
296                if (section.isAllowOverlap()) sectionEl.addAttribute("canOverlap", "true");
297                if (section.isCommon()) sectionEl.addAttribute("common", "true");
298            }
299            requestEl.addAttribute("load", sDoubleFormat.format(request.getLoad()));
300            requestEl.addAttribute("sameCourse", Constants.preferenceLevel2preference(request.getSameCoursePreference()));
301            requestEl.addAttribute("sameCommon", Constants.preferenceLevel2preference(request.getSameCommonPreference()));
302            for (Preference<Attribute> pref: request.getAttributePreferences()) {
303                Element attributeEl = requestEl.addElement("attribute");
304                if (pref.getTarget().getAttributeId() != null)
305                    attributeEl.addAttribute("id", String.valueOf(pref.getTarget().getAttributeId()));
306                attributeEl.addAttribute("name", pref.getTarget().getAttributeName());
307                attributeEl.addAttribute("type", pref.getTarget().getType().getTypeName());
308                attributeEl.addAttribute("preference", (pref.isRequired() ? "R" : pref.isProhibited() ? "P" : String.valueOf(pref.getPreference())));
309                if (pref.getTarget().getParentAttribute() != null && pref.getTarget().getParentAttribute().getAttributeId() != null)
310                    attributeEl.addAttribute("parent", String.valueOf(pref.getTarget().getParentAttribute().getAttributeId()));
311            }
312            for (Preference<Instructor> pref: request.getInstructorPreferences()) {
313                Element instructorEl = requestEl.addElement("instructor");
314                instructorEl.addAttribute("id", String.valueOf(pref.getTarget().getInstructorId()));
315                if (pref.getTarget().hasExternalId())
316                    instructorEl.addAttribute("externalId", pref.getTarget().getExternalId());
317                if (pref.getTarget().hasName())
318                    instructorEl.addAttribute("name", pref.getTarget().getName());
319                instructorEl.addAttribute("preference", (pref.isRequired() ? "R" : pref.isProhibited() ? "P" : String.valueOf(pref.getPreference())));
320            }
321            if (saveBest)
322                for (TeachingRequest.Variable variable: request.getVariables()) {
323                    if (variable.getBestAssignment() != null) {
324                        Instructor instructor = variable.getBestAssignment().getInstructor();
325                        Element instructorEl = requestEl.addElement("best-instructor");
326                        instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId()));
327                        if (request.getNrInstructors() != 1)
328                            instructorEl.addAttribute("index", String.valueOf(variable.getInstructorIndex()));
329                        if (instructor.hasExternalId())
330                            instructorEl.addAttribute("externalId", instructor.getExternalId());
331                        if (instructor.hasName())
332                            instructorEl.addAttribute("name", instructor.getName());
333                    }                    
334                }
335            if (saveInitial)
336                for (TeachingRequest.Variable variable: request.getVariables()) {
337                    if (variable.getInitialAssignment() != null) {
338                        Instructor instructor = variable.getInitialAssignment().getInstructor();
339                        Element instructorEl = requestEl.addElement("initial-instructor");
340                        instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId()));
341                        if (request.getNrInstructors() != 1)
342                            instructorEl.addAttribute("index", String.valueOf(variable.getInstructorIndex()));
343                        if (instructor.hasExternalId())
344                            instructorEl.addAttribute("externalId", instructor.getExternalId());
345                        if (instructor.hasName())
346                            instructorEl.addAttribute("name", instructor.getName());
347                    }
348                }
349            if (saveSolution)
350                for (TeachingRequest.Variable variable: request.getVariables()) {
351                    TeachingAssignment ta = assignment.getValue(variable);
352                    if (ta != null) {
353                        Instructor instructor = ta.getInstructor();
354                        Element instructorEl = requestEl.addElement("assigned-instructor");
355                        instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId()));
356                        if (request.getNrInstructors() != 1)
357                            instructorEl.addAttribute("index", String.valueOf(variable.getInstructorIndex()));
358                        if (instructor.hasExternalId())
359                            instructorEl.addAttribute("externalId", instructor.getExternalId());
360                        if (instructor.hasName())
361                            instructorEl.addAttribute("name", instructor.getName());
362                    }
363                }
364        }
365        Element instructorsEl = root.addElement("instructors");
366        for (Instructor instructor: getInstructors()) {
367            Element instructorEl = instructorsEl.addElement("instructor");
368            instructorEl.addAttribute("id", String.valueOf(instructor.getInstructorId()));
369            if (instructor.hasExternalId())
370                instructorEl.addAttribute("externalId", instructor.getExternalId());
371            if (instructor.hasName())
372                instructorEl.addAttribute("name", instructor.getName());
373            if (instructor.getPreference() != 0)
374                instructorEl.addAttribute("preference", String.valueOf(instructor.getPreference()));
375            if (instructor.getBackToBackPreference() != 0)
376                instructorEl.addAttribute("btb", String.valueOf(instructor.getBackToBackPreference()));
377            if (instructor.getSameDaysPreference() != 0)
378                instructorEl.addAttribute("same-days", String.valueOf(instructor.getSameDaysPreference()));
379            if (instructor.getSameRoomPreference() != 0)
380                instructorEl.addAttribute("same-room", String.valueOf(instructor.getSameRoomPreference()));
381            for (Attribute attribute: instructor.getAttributes()) {
382                Element attributeEl = instructorEl.addElement("attribute");
383                if (attribute.getAttributeId() != null)
384                    attributeEl.addAttribute("id", String.valueOf(attribute.getAttributeId()));
385                attributeEl.addAttribute("name", attribute.getAttributeName());
386                attributeEl.addAttribute("type", attribute.getType().getTypeName());
387                if (attribute.getParentAttribute() != null && attribute.getParentAttribute().getAttributeId() != null)
388                    attributeEl.addAttribute("parent", String.valueOf(attribute.getParentAttribute().getAttributeId()));
389            }
390            instructorEl.addAttribute("maxLoad", sDoubleFormat.format(instructor.getMaxLoad()));
391            for (Distribution d: instructor.getDistributions()) {
392                Element dEl = instructorEl.addElement("distribution");
393                dEl.addAttribute("type", d.getType().reference());
394                dEl.addAttribute("preference", d.getPreference());
395                dEl.addAttribute("name", d.getType().getName());
396            }
397            for (Preference<TimeLocation> tp: instructor.getTimePreferences()) {
398                
399                Element timeEl = instructorEl.addElement("time");
400                TimeLocation tl = tp.getTarget();
401                timeEl.addAttribute("days", sDF7.format(Long.parseLong(Integer.toBinaryString(tl.getDayCode()))));
402                timeEl.addAttribute("start", String.valueOf(tl.getStartSlot()));
403                timeEl.addAttribute("length", String.valueOf(tl.getLength()));
404                if (tl.getBreakTime() != 0)
405                    timeEl.addAttribute("breakTime", String.valueOf(tl.getBreakTime()));
406                if (tl.getTimePatternId() != null)
407                    timeEl.addAttribute("pattern", tl.getTimePatternId().toString());
408                if (tl.getDatePatternId() != null)
409                    timeEl.addAttribute("datePattern", tl.getDatePatternId().toString());
410                if (tl.getDatePatternName() != null && !tl.getDatePatternName().isEmpty())
411                    timeEl.addAttribute("datePatternName", tl.getDatePatternName());
412                if (tl.getWeekCode() != null)
413                    timeEl.addAttribute("dates", bitset2string(tl.getWeekCode()));
414                timeEl.addAttribute("preference", tp.isProhibited() ? "P" : tp.isRequired() ? "R" : String.valueOf(tp.getPreference()));
415                if (tp.getTarget() instanceof EnrolledClass) {
416                    Element classEl = timeEl.addElement("section");
417                    Element courseEl = null;
418                    EnrolledClass ec = (EnrolledClass)tp.getTarget();
419                    if (ec.getCourseId() != null || ec.getCourse() != null) {
420                        courseEl = timeEl.addElement("course");
421                        if (ec.getCourseId() != null) courseEl.addAttribute("id", String.valueOf(ec.getCourseId()));
422                        if (ec.getCourse() != null) courseEl.addAttribute("name", ec.getCourse());
423                    }
424                    if (ec.getClassId() != null) classEl.addAttribute("id", String.valueOf(ec.getClassId()));
425                    if (ec.getType() != null) classEl.addAttribute("type", ec.getType());
426                    if (ec.getSection() != null) classEl.addAttribute("name", ec.getSection());
427                    if (ec.getExternalId() != null) classEl.addAttribute("externalId", ec.getExternalId());
428                    if (ec.getRoom() != null) classEl.addAttribute("room", ec.getRoom());
429                    classEl.addAttribute("role", ec.isInstructor() ? "instructor" : "student");
430                } else {
431                    timeEl.setText(tl.getLongName(false));
432                }
433            }
434            for (Preference<Course> cp: instructor.getCoursePreferences()) {
435                Element courseEl = instructorEl.addElement("course");
436                Course course = cp.getTarget();
437                if (course.getCourseId() != null)
438                    courseEl.addAttribute("id", String.valueOf(course.getCourseId()));
439                if (course.getCourseName() != null)
440                    courseEl.addAttribute("name", String.valueOf(course.getCourseName()));
441                courseEl.addAttribute("preference", cp.isProhibited() ? "P" : cp.isRequired() ? "R" : String.valueOf(cp.getPreference()));
442            }
443        }
444        Element constraintsEl = root.addElement("constraints");
445        for (Constraint<TeachingRequest.Variable, TeachingAssignment> c: constraints()) {
446            if (c instanceof SameInstructorConstraint) {
447                SameInstructorConstraint si = (SameInstructorConstraint) c;
448                Element sameInstEl = constraintsEl.addElement("same-instructor");
449                if (si.getConstraintId() != null)
450                    sameInstEl.addAttribute("id", String.valueOf(si.getConstraintId()));
451                if (si.getName() != null)
452                    sameInstEl.addAttribute("name", si.getName());
453                sameInstEl.addAttribute("preference", Constants.preferenceLevel2preference(si.getPreference()));
454                for (TeachingRequest.Variable request: c.variables()) {
455                    Element assignmentEl = sameInstEl.addElement("request");
456                    assignmentEl.addAttribute("id", String.valueOf(request.getRequest().getRequestId()));
457                    if (request.getRequest().getNrInstructors() != 1)
458                        assignmentEl.addAttribute("index", String.valueOf(request.getInstructorIndex()));
459                }
460            } else if (c instanceof SameLinkConstraint) {
461                SameLinkConstraint si = (SameLinkConstraint) c;
462                Element sameInstEl = constraintsEl.addElement("same-link");
463                if (si.getConstraintId() != null)
464                    sameInstEl.addAttribute("id", String.valueOf(si.getConstraintId()));
465                if (si.getName() != null)
466                    sameInstEl.addAttribute("name", si.getName());
467                sameInstEl.addAttribute("preference", Constants.preferenceLevel2preference(si.getPreference()));
468                for (TeachingRequest.Variable request: c.variables()) {
469                    Element assignmentEl = sameInstEl.addElement("request");
470                    assignmentEl.addAttribute("id", String.valueOf(request.getRequest().getRequestId()));
471                    if (request.getRequest().getNrInstructors() != 1)
472                        assignmentEl.addAttribute("index", String.valueOf(request.getInstructorIndex()));
473                }
474            }
475        }
476        return document;
477    }
478    
479    /**
480     * Load the problem (and its solution) from an XML format
481     * @param document XML document
482     * @param assignment current assignment
483     * @return true, if the problem was successfully loaded in
484     */
485    public boolean load(Document document, Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) {
486        boolean loadInitial = getProperties().getPropertyBoolean("Xml.LoadInitial", true);
487        boolean loadBest = getProperties().getPropertyBoolean("Xml.LoadBest", true);
488        boolean loadSolution = getProperties().getPropertyBoolean("Xml.LoadSolution", true);
489        String defaultBtb = getProperties().getProperty("Defaults.BackToBack", "0");
490        String defaultSameDays = getProperties().getProperty("Defaults.SameDays", "0");
491        String defaultSameRoom = getProperties().getProperty("Defaults.SameRoom", "0");
492        String defaultConjunctive = getProperties().getProperty("Defaults.Conjunctive", "false");
493        String defaultRequired = getProperties().getProperty("Defaults.Required", "false");
494        String defaultSameCourse = getProperties().getProperty("Defaults.SameCourse", "R");
495        String defaultSameCommon = getProperties().getProperty("Defaults.SameCommon", "R");
496        Element root = document.getRootElement();
497        if (!"instructor-schedule".equals(root.getName()))
498            return false;
499        Map<String, Attribute.Type> types = new HashMap<String, Attribute.Type>();
500        Map<Long, Attribute> attributes = new HashMap<Long, Attribute>();
501        Map<Long, Long> parents = new HashMap<Long, Long>();
502        if (root.element("attributes") != null) {
503            for (Iterator<?> i = root.element("attributes").elementIterator("type"); i.hasNext();) {
504                Element typeEl = (Element) i.next();
505                Attribute.Type type = new Attribute.Type(
506                        Long.parseLong(typeEl.attributeValue("id")),
507                        typeEl.attributeValue("name"),
508                        "true".equalsIgnoreCase(typeEl.attributeValue("conjunctive", defaultConjunctive)),
509                        "true".equalsIgnoreCase(typeEl.attributeValue("required", defaultRequired)));
510                addAttributeType(type);
511                if (type.getTypeName() != null)
512                    types.put(type.getTypeName(), type);
513                for (Iterator<?> j = typeEl.elementIterator("attribute"); j.hasNext();) {
514                    Element attributeEl = (Element) j.next();
515                    Attribute attribute = new Attribute(
516                            Long.parseLong(attributeEl.attributeValue("id")),
517                            attributeEl.attributeValue("name"),
518                            type);
519                    attributes.put(attribute.getAttributeId(), attribute);
520                    if (attributeEl.attributeValue("parent") != null)
521                        parents.put(attribute.getAttributeId(), Long.parseLong(attributeEl.attributeValue("parent")));
522                }
523            }
524        }
525        Map<Long, Course> courses = new HashMap<Long, Course>();
526        if (root.element("courses") != null) {
527            for (Iterator<?> i = root.element("courses").elementIterator("course"); i.hasNext();) {
528                Element courseEl = (Element) i.next();
529                Course course = new Course(
530                        Long.parseLong(courseEl.attributeValue("id")),
531                        courseEl.attributeValue("name"));
532                courses.put(course.getCourseId(), course);
533            }
534        }
535        Map<Long, Instructor> instructors = new HashMap<Long, Instructor>();
536        for (Iterator<?> i = root.element("instructors").elementIterator("instructor"); i.hasNext();) {
537            Element instructorEl = (Element) i.next();
538            Instructor instructor = new Instructor(
539                    Long.parseLong(instructorEl.attributeValue("id")),
540                    instructorEl.attributeValue("externalId"),
541                    instructorEl.attributeValue("name"),
542                    string2preference(instructorEl.attributeValue("preference")),
543                    Float.parseFloat(instructorEl.attributeValue("maxLoad", "0")));
544            instructor.setBackToBackPreference(Integer.valueOf(instructorEl.attributeValue("btb", defaultBtb)));
545            instructor.setSameDaysPreference(Integer.valueOf(instructorEl.attributeValue("same-days", defaultSameDays)));
546            instructor.setSameRoomPreference(Integer.valueOf(instructorEl.attributeValue("same-room", defaultSameRoom)));
547            for (Iterator<?> j = instructorEl.elementIterator("attribute"); j.hasNext();) {
548                Element f = (Element) j.next();
549                Long attributeId = Long.valueOf(f.attributeValue("id"));
550                Attribute attribute = attributes.get(attributeId);
551                if (attribute == null) {
552                    Attribute.Type type = types.get(f.attributeValue("type"));
553                    if (type == null) {
554                        type = new Attribute.Type(types.size(), f.attributeValue("type"),
555                                "true".equalsIgnoreCase(f.attributeValue("conjunctive", defaultConjunctive)),
556                                "true".equalsIgnoreCase(f.attributeValue("required", defaultRequired)));
557                        types.put(type.getTypeName(), type);
558                    }
559                    attribute = new Attribute(attributeId, f.attributeValue("name"), type);
560                    attributes.put(attributeId, attribute);
561                    if (f.attributeValue("parent") != null)
562                        parents.put(attribute.getAttributeId(), Long.parseLong(f.attributeValue("parent")));
563                }
564                instructor.addAttribute(attribute);
565            }
566            for (Iterator<?> j = instructorEl.elementIterator("distribution"); j.hasNext();) {
567                Element dEl = (Element) j.next();
568                instructor.addDistribution(dEl.attributeValue("type"), dEl.attributeValue("preference", "0"), dEl.attributeValue("name"));
569            }
570            for (Iterator<?> j = instructorEl.elementIterator("time"); j.hasNext();) {
571                Element f = (Element) j.next();
572                Element classEl = f.element("section");
573                Element courseEl = f.element("course");
574                TimeLocation time = null;
575                if (classEl != null) {
576                    time = new EnrolledClass(
577                            courseEl == null || courseEl.attributeValue("id") == null ? null : Long.valueOf(courseEl.attributeValue("id")),
578                            classEl.attributeValue("id") == null ? null : Long.valueOf(classEl.attributeValue("id")),
579                            courseEl == null ? null : courseEl.attributeValue("name"),
580                            classEl.attributeValue("type"),
581                            classEl.attributeValue("name"),
582                            classEl.attributeValue("externalId"),
583                            Integer.parseInt(f.attributeValue("days"), 2),
584                            Integer.parseInt(f.attributeValue("start")),
585                            Integer.parseInt(f.attributeValue("length")),
586                            f.attributeValue("datePattern") == null ? null : Long.valueOf(f.attributeValue("datePattern")),
587                            f.attributeValue("datePatternName", ""),
588                            createBitSet(f.attributeValue("dates")),
589                            Integer.parseInt(f.attributeValue("breakTime", "0")),
590                            classEl.attributeValue("room"),
591                            "instructor".equalsIgnoreCase(classEl.attributeValue("role", "instructor")));
592                } else {
593                    time = new TimeLocation(
594                            Integer.parseInt(f.attributeValue("days"), 2),
595                            Integer.parseInt(f.attributeValue("start")),
596                            Integer.parseInt(f.attributeValue("length")), 0, 0,
597                            f.attributeValue("datePattern") == null ? null : Long.valueOf(f.attributeValue("datePattern")),
598                            f.attributeValue("datePatternName", ""),
599                            createBitSet(f.attributeValue("dates")),
600                            Integer.parseInt(f.attributeValue("breakTime", "0")));
601                }
602                if (f.attributeValue("pattern") != null)
603                    time.setTimePatternId(Long.valueOf(f.attributeValue("pattern")));
604                instructor.addTimePreference(new Preference<TimeLocation>(time, string2preference(f.attributeValue("preference"))));
605            }
606            for (Iterator<?> j = instructorEl.elementIterator("course"); j.hasNext();) {
607                Element f = (Element) j.next();
608                instructor.addCoursePreference(new Preference<Course>(new Course(Long.parseLong(f.attributeValue("id")), f.attributeValue("name")), string2preference(f.attributeValue("preference"))));
609            }
610            addInstructor(instructor);
611            instructors.put(instructor.getInstructorId(), instructor);
612        }
613        Map<Long, TeachingRequest> requests = new HashMap<Long, TeachingRequest>();
614        Map<TeachingRequest, Map<Integer, Instructor>> current = new HashMap<TeachingRequest, Map<Integer, Instructor>>();
615        Map<TeachingRequest, Map<Integer, Instructor>> best = new HashMap<TeachingRequest, Map<Integer, Instructor>>();
616        Map<TeachingRequest, Map<Integer, Instructor>> initial = new HashMap<TeachingRequest, Map<Integer, Instructor>>();
617        for (Iterator<?> i = root.element("teaching-requests").elementIterator("request"); i.hasNext();) {
618            Element requestEl = (Element) i.next();
619            Element courseEl = requestEl.element("course");
620            Course course = null;
621            if (courseEl != null) {
622                Long courseId = Long.valueOf(courseEl.attributeValue("id"));
623                course = courses.get(courseId);
624                if (course == null) {
625                    course = new Course(courseId, courseEl.attributeValue("name"));
626                }
627            } else {
628                course = courses.get(Long.valueOf(requestEl.attributeValue("course")));
629            }
630            List<Section> sections = new ArrayList<Section>();
631            for (Iterator<?> j = requestEl.elementIterator("section"); j.hasNext();) {
632                Element f = (Element) j.next();
633                TimeLocation time = null;
634                Element timeEl = f.element("time");
635                if (timeEl != null) {
636                    time = new TimeLocation(
637                            Integer.parseInt(timeEl.attributeValue("days"), 2),
638                            Integer.parseInt(timeEl.attributeValue("start")),
639                            Integer.parseInt(timeEl.attributeValue("length")), 0, 0,
640                            timeEl.attributeValue("datePattern") == null ? null : Long.valueOf(timeEl.attributeValue("datePattern")),
641                            timeEl.attributeValue("datePatternName", ""),
642                            createBitSet(timeEl.attributeValue("dates")),
643                            Integer.parseInt(timeEl.attributeValue("breakTime", "0")));
644                    if (timeEl.attributeValue("pattern") != null)
645                        time.setTimePatternId(Long.valueOf(timeEl.attributeValue("pattern")));
646                }
647                Section section = new Section(
648                        Long.valueOf(f.attributeValue("id")),
649                        f.attributeValue("externalId"),
650                        f.attributeValue("type"),
651                        f.attributeValue("name"),
652                        time,
653                        f.attributeValue("room"),
654                        "true".equalsIgnoreCase(f.attributeValue("canOverlap", "false")),
655                        "true".equalsIgnoreCase(f.attributeValue("common", "false")));
656                sections.add(section);
657            }
658            TeachingRequest request = new TeachingRequest(
659                    Long.parseLong(requestEl.attributeValue("id")),
660                    Integer.parseInt(requestEl.attributeValue("nrInstructors", "1")),
661                    course,
662                    Float.valueOf(requestEl.attributeValue("load", "0")),
663                    sections,
664                    Constants.preference2preferenceLevel(requestEl.attributeValue("sameCourse", defaultSameCourse)),
665                    Constants.preference2preferenceLevel(requestEl.attributeValue("sameCommon", defaultSameCommon)));
666            requests.put(request.getRequestId(), request);
667            for (Iterator<?> j = requestEl.elementIterator("attribute"); j.hasNext();) {
668                Element f = (Element) j.next();
669                Long attributeId = Long.valueOf(f.attributeValue("id"));
670                Attribute attribute = attributes.get(attributeId);
671                if (attribute == null) {
672                    Attribute.Type type = types.get(f.attributeValue("type"));
673                    if (type == null) {
674                        type = new Attribute.Type(types.size(), f.attributeValue("type"),
675                                "true".equalsIgnoreCase(f.attributeValue("conjunctive", defaultConjunctive)),
676                                "true".equalsIgnoreCase(f.attributeValue("required", defaultRequired)));
677                        types.put(type.getTypeName(), type);
678                    }
679                    attribute = new Attribute(attributeId, f.attributeValue("name"), type);
680                    attributes.put(attributeId, attribute);
681                    if (f.attributeValue("parent") != null)
682                        parents.put(attribute.getAttributeId(), Long.parseLong(f.attributeValue("parent")));
683                }
684                request.addAttributePreference(new Preference<Attribute>(attribute, string2preference(f.attributeValue("preference"))));
685            }
686            for (Iterator<?> j = requestEl.elementIterator("instructor"); j.hasNext();) {
687                Element f = (Element) j.next();
688                Long instructorId = Long.valueOf(f.attributeValue("id"));
689                Instructor instructor = instructors.get(instructorId);
690                if (instructor != null)
691                    request.addInstructorPreference(new Preference<Instructor>(instructor, string2preference(f.attributeValue("preference"))));
692            }
693            if (loadBest) {
694                for (Iterator<?> j = requestEl.elementIterator("best-instructor"); j.hasNext();) {
695                    Element f = (Element) j.next();
696                    Map<Integer, Instructor> idx2inst = best.get(request);
697                    if (idx2inst == null) {
698                        idx2inst = new HashMap<Integer, Instructor>();
699                        best.put(request, idx2inst);
700                    }
701                    int index = 1 + Integer.parseInt(f.attributeValue("index", String.valueOf(idx2inst.size())));
702                    Instructor instructor = instructors.get(Long.valueOf(f.attributeValue("id")));
703                    if (instructor != null)
704                        idx2inst.put(index, instructor);
705                }
706            }
707            if (loadInitial) {
708                for (Iterator<?> j = requestEl.elementIterator("initial-instructor"); j.hasNext();) {
709                    Element f = (Element) j.next();
710                    Map<Integer, Instructor> idx2inst = initial.get(request);
711                    if (idx2inst == null) {
712                        idx2inst = new HashMap<Integer, Instructor>();
713                        initial.put(request, idx2inst);
714                    }
715                    int index = 1 + Integer.parseInt(f.attributeValue("index", String.valueOf(idx2inst.size())));
716                    Instructor instructor = instructors.get(Long.valueOf(f.attributeValue("id")));
717                    if (instructor != null)
718                        idx2inst.put(index, instructor);
719                }
720            }
721            if (loadSolution) {
722                for (Iterator<?> j = requestEl.elementIterator("assigned-instructor"); j.hasNext();) {
723                    Element f = (Element) j.next();
724                    Map<Integer, Instructor> idx2inst = current.get(request);
725                    if (idx2inst == null) {
726                        idx2inst = new HashMap<Integer, Instructor>();
727                        current.put(request, idx2inst);
728                    }
729                    int index = Integer.parseInt(f.attributeValue("index", String.valueOf(idx2inst.size())));
730                    Instructor instructor = instructors.get(Long.valueOf(f.attributeValue("id")));
731                    if (instructor != null)
732                        idx2inst.put(index, instructor);
733                }
734            }
735            addRequest(request);
736        }
737        if (root.element("constraints") != null) {
738            for (Iterator<?> i = root.element("constraints").elementIterator(); i.hasNext();) {
739                Element constraintEl = (Element) i.next();
740                Constraint<TeachingRequest.Variable, TeachingAssignment> constraint = null;
741                if ("same-link".equals(constraintEl.getName())) {
742                    constraint = new SameLinkConstraint(
743                            (constraintEl.attributeValue("id") == null ? null : Long.valueOf(constraintEl.attributeValue("id"))),
744                            constraintEl.attributeValue("name"),
745                            constraintEl.attributeValue("preference"));
746                } else if ("same-instructor".equals(constraintEl.getName())) {
747                    constraint = new SameInstructorConstraint(
748                            (constraintEl.attributeValue("id") == null ? null : Long.valueOf(constraintEl.attributeValue("id"))),
749                            constraintEl.attributeValue("name"),
750                            constraintEl.attributeValue("preference"));
751                }
752                if (constraint != null) {
753                    for (Iterator<?> j = constraintEl.elementIterator("request"); j.hasNext();) {
754                        Element f = (Element) j.next();
755                        TeachingRequest request = requests.get(Long.valueOf(f.attributeValue("id")));
756                        if (request != null) {
757                            int index = Integer.valueOf(f.attributeValue("index", "0"));
758                            if (index >= 0 && index < request.getNrInstructors())
759                                constraint.addVariable(request.getVariables()[index]);
760                        }
761                    }
762                    addConstraint(constraint);
763                }
764            }            
765        }
766        for (Map.Entry<Long, Long> e: parents.entrySet())
767            attributes.get(e.getKey()).setParentAttribute(attributes.get(e.getValue()));
768        for (Map.Entry<TeachingRequest, Map<Integer, Instructor>> e1: best.entrySet())
769            for (Map.Entry<Integer, Instructor> e2: e1.getValue().entrySet())
770                if (e2.getKey() >= 0 && e2.getKey() < e1.getKey().getNrInstructors()) {
771                    TeachingRequest.Variable variable = e1.getKey().getVariables()[e2.getKey()];
772                    variable.setBestAssignment(new TeachingAssignment(variable, e2.getValue()), 0l);
773                }
774
775        for (Map.Entry<TeachingRequest, Map<Integer, Instructor>> e1: initial.entrySet())
776            for (Map.Entry<Integer, Instructor> e2: e1.getValue().entrySet())
777                if (e2.getKey() >= 0 && e2.getKey() < e1.getKey().getNrInstructors()) {
778                    TeachingRequest.Variable variable = e1.getKey().getVariables()[e2.getKey()];
779                    variable.setInitialAssignment(new TeachingAssignment(variable, e2.getValue()));
780                }
781        
782        if (!current.isEmpty()) {
783            for (Map.Entry<TeachingRequest, Map<Integer, Instructor>> e1: current.entrySet())
784                for (Map.Entry<Integer, Instructor> e2: e1.getValue().entrySet())
785                    if (e2.getKey() >= 0 && e2.getKey() < e1.getKey().getNrInstructors()) {
786                        TeachingRequest.Variable variable = e1.getKey().getVariables()[e2.getKey()];
787                        TeachingAssignment ta = new TeachingAssignment(variable, e2.getValue());
788                        Set<TeachingAssignment> conf = conflictValues(assignment, ta);
789                        if (conf.isEmpty()) {
790                            assignment.assign(0, ta);
791                        } else {
792                            sLog.error("Unable to assign " + ta.getName() + " to " + variable.getName());
793                            sLog.error("Conflicts:" + ToolBox.dict2string(conflictConstraints(assignment, ta), 2));
794                        }
795                    }
796        }
797        
798        return true;
799    }
800    
801    /** Convert bitset to a bit string */
802    protected static String bitset2string(BitSet b) {
803        StringBuffer sb = new StringBuffer();
804        for (int i = 0; i < b.length(); i++)
805            sb.append(b.get(i) ? "1" : "0");
806        return sb.toString();
807    }
808
809    /** Create BitSet from a bit string */
810    protected static BitSet createBitSet(String bitString) {
811        if (bitString == null) return null;
812        BitSet ret = new BitSet(bitString.length());
813        for (int i = 0; i < bitString.length(); i++)
814            if (bitString.charAt(i) == '1')
815                ret.set(i);
816        return ret;
817    }
818    
819    /** Convert preference string to a preference value */
820    protected static int string2preference(String pref) {
821        if (pref == null || pref.isEmpty()) return 0;
822        if (Constants.sPreferenceRequired.equals(pref))
823            return Constants.sPreferenceLevelRequired;
824        if (Constants.sPreferenceProhibited.equals(pref))
825            return Constants.sPreferenceLevelProhibited;
826        return Integer.valueOf(pref);
827    }
828    
829    private List<BitSet> iWeeks = null;
830    /**
831     * The method creates date patterns (bitsets) which represent the weeks of a
832     * semester.
833     *      
834     * @return a list of BitSets which represents the weeks of a semester.
835     */
836    public List<BitSet> getWeeks() {
837        if (iWeeks == null) {
838            String defaultDatePattern = getProperties().getProperty("DatePattern.CustomDatePattern", null);
839            if (defaultDatePattern == null){                
840                defaultDatePattern = getProperties().getProperty("DatePattern.Default");
841            }
842            BitSet fullTerm = null;
843            if (defaultDatePattern == null) {
844                // Take the date pattern that is being used most often
845                Map<Long, Integer> counter = new HashMap<Long, Integer>();
846                int max = 0; String name = null; Long id = null;
847                for (TeachingRequest.Variable tr: variables()) {
848                    for (Section section: tr.getSections()) {
849                        TimeLocation time = section.getTime();
850                        if (time.getWeekCode() != null && time.getDatePatternId() != null) {
851                            int count = 1;
852                            if (counter.containsKey(time.getDatePatternId()))
853                                count += counter.get(time.getDatePatternId());
854                            counter.put(time.getDatePatternId(), count);
855                            if (count > max) {
856                                max = count; fullTerm = time.getWeekCode(); name = time.getDatePatternName(); id = time.getDatePatternId();
857                            }
858                        }
859                    }
860                }
861                sLog.info("Using date pattern " + name + " (id " + id + ") as the default.");
862            } else {
863                // Create default date pattern
864                fullTerm = new BitSet(defaultDatePattern.length());
865                for (int i = 0; i < defaultDatePattern.length(); i++) {
866                    if (defaultDatePattern.charAt(i) == 49) {
867                        fullTerm.set(i);
868                    }
869                }
870            }
871            
872            if (fullTerm == null) return null;
873            
874            iWeeks = new ArrayList<BitSet>();
875            if (getProperties().getPropertyBoolean("DatePattern.ShiftWeeks", false)) {
876                // Cut date pattern into weeks (each week takes 7 consecutive bits, starting on the next positive bit)
877                for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) {
878                    if (!fullTerm.get(i)) {
879                        i++; continue;
880                    }
881                    BitSet w = new BitSet(i + 7);
882                    for (int j = 0; j < 7; j++)
883                        if (fullTerm.get(i + j)) w.set(i + j);
884                    iWeeks.add(w);
885                    i += 7;
886                }                
887            } else {
888                // Cut date pattern into weeks (each week takes 7 consecutive bits starting on the first bit of the default date pattern, no pauses between weeks)
889                for (int i = fullTerm.nextSetBit(0); i < fullTerm.length(); ) {
890                    BitSet w = new BitSet(i + 7);
891                    for (int j = 0; j < 7; j++)
892                        if (fullTerm.get(i + j)) w.set(i + j);
893                    iWeeks.add(w);
894                    i += 7;
895                }
896            }
897        }
898        return iWeeks;
899    }
900}