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