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