001package org.cpsolver.instructor.test;
002
003import java.io.BufferedReader;
004import java.io.File;
005import java.io.FileReader;
006import java.io.IOException;
007import java.io.PrintWriter;
008import java.text.DecimalFormat;
009import java.util.ArrayList;
010import java.util.Collections;
011import java.util.Comparator;
012import java.util.HashMap;
013import java.util.HashSet;
014import java.util.List;
015import java.util.Map;
016import java.util.Set;
017import java.util.TreeSet;
018
019import org.apache.log4j.Logger;
020import org.cpsolver.coursett.Constants;
021import org.cpsolver.coursett.model.TimeLocation;
022import org.cpsolver.ifs.assignment.Assignment;
023import org.cpsolver.ifs.model.Constraint;
024import org.cpsolver.ifs.util.DataProperties;
025import org.cpsolver.ifs.util.ToolBox;
026import org.cpsolver.instructor.Test;
027import org.cpsolver.instructor.constraints.SameInstructorConstraint;
028import org.cpsolver.instructor.constraints.SameLinkConstraint;
029import org.cpsolver.instructor.criteria.DifferentLecture;
030import org.cpsolver.instructor.model.Course;
031import org.cpsolver.instructor.model.Instructor;
032import org.cpsolver.instructor.model.Attribute;
033import org.cpsolver.instructor.model.Preference;
034import org.cpsolver.instructor.model.Section;
035import org.cpsolver.instructor.model.TeachingAssignment;
036import org.cpsolver.instructor.model.TeachingRequest;
037
038/**
039 * Math teaching assistant assignment problem. Different file format for the input data.
040 * 
041 * @version IFS 1.3 (Instructor Sectioning)<br>
042 *          Copyright (C) 2016 Tomas Muller<br>
043 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
044 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
045 * <br>
046 *          This library is free software; you can redistribute it and/or modify
047 *          it under the terms of the GNU Lesser General Public License as
048 *          published by the Free Software Foundation; either version 3 of the
049 *          License, or (at your option) any later version. <br>
050 * <br>
051 *          This library is distributed in the hope that it will be useful, but
052 *          WITHOUT ANY WARRANTY; without even the implied warranty of
053 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
054 *          Lesser General Public License for more details. <br>
055 * <br>
056 *          You should have received a copy of the GNU Lesser General Public
057 *          License along with this library; if not see
058 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
059 */
060public class MathTest extends Test {
061    private static Logger sLog = Logger.getLogger(MathTest.class);
062    
063    public MathTest(DataProperties properties) {
064        super(properties);
065        removeCriterion(DifferentLecture.class);
066    }
067    
068    public String getLevel(Instructor instructor) {
069        for (Attribute attribute: instructor.getAttributes())
070            if (attribute.getType().getTypeName().equals("Level")) return attribute.getAttributeName();
071        return null;
072    }
073    
074    public String toString(Instructor instructor) {
075        StringBuffer sb = new StringBuffer();
076        sb.append(instructor.getExternalId());
077        sb.append(",\"" + instructor.getAvailable() + "\"");
078        Collections.sort(instructor.getCoursePreferences(), new Comparator<Preference<Course>>() {
079            @Override
080            public int compare(Preference<Course> p1, Preference<Course> p2) {
081                if (p1.getPreference() == p2.getPreference())
082                    return p1.getTarget().getCourseName().compareTo(p2.getTarget().getCourseName());
083                return p1.getPreference() < p2.getPreference() ? -1 : 1;
084            }
085        });
086        for (int i = 0; i < 3; i++) {
087            Preference<Course> p = (i < instructor.getCoursePreferences().size() ? instructor.getCoursePreferences().get(i) : null);
088            sb.append("," + (p == null ? "" : p.getTarget().getCourseName()));
089        }
090        sb.append("," + (instructor.getPreference() == 0 ? "Yes" : "No"));
091        sb.append("," + (instructor.isBackToBackPreferred() ? "1" : instructor.isBackToBackDiscouraged() ? "-1" : "0"));
092        sb.append("," + new DecimalFormat("0.0").format(instructor.getMaxLoad()));
093        String level = getLevel(instructor);
094        sb.append("," + (level == null ? "" : level)); 
095        return sb.toString();
096    }
097    
098    public String getLink(TeachingRequest request) {
099        for (Constraint<TeachingRequest, TeachingAssignment> c: request.constraints()) {
100            if (c instanceof SameLinkConstraint)
101                return c.getName().substring(c.getName().indexOf('-') + 1);
102        }
103        return null;
104    }
105    
106    public Long getAssignmentId(TeachingRequest request) {
107        for (Constraint<TeachingRequest, TeachingAssignment> c: request.constraints()) {
108            if (c instanceof SameInstructorConstraint && ((SameInstructorConstraint) c).getConstraintId() != null)
109                return ((SameInstructorConstraint) c).getConstraintId();
110        }
111        return null;
112    }
113    
114    public int countDiffLinks(Set<TeachingAssignment> assignments) {
115        Set<String> links = new HashSet<String>();
116        for (TeachingAssignment assignment : assignments) {
117            String link = getLink(assignment.variable());
118            if (link != null)
119                links.add(link);
120        }
121        return Math.max(0, links.size() - 1);
122    }
123    
124    public String toString(TeachingRequest request) {
125        StringBuffer sb = new StringBuffer();
126        Long assId = getAssignmentId(request);
127        sb.append(assId == null ? "" : assId);
128        sb.append("," + request.getCourse().getCourseName());
129        Section section = request.getSections().get(0);
130        sb.append("," + section.getSectionName());
131        sb.append("," + section.getTimeName(true));
132        sb.append(",\"" + (section.hasRoom() ? section.getRoom() : "") + "\"");
133        String link = getLink(request);
134        sb.append("," + (link == null ? "" : link));
135        Map<String, Integer> levels = new HashMap<String, Integer>();
136        for (Preference<Attribute> p: request.getAttributePreferences())
137            levels.put(p.getTarget().getAttributeName(), - p.getPreference());
138        sb.append(",\"" + levels + "\"");
139        sb.append("," + new DecimalFormat("0.0").format(request.getLoad()));
140        return sb.toString();
141    }
142    
143    @Override
144    protected boolean load(File dir, Assignment<TeachingRequest, TeachingAssignment> assignment) {
145        if (!dir.isDirectory())
146            return super.load(dir, assignment);
147        try {
148            String line = null;
149            BufferedReader r = new BufferedReader(new FileReader(new File(dir, "courses.csv")));
150            Map<String, Course> courses = new HashMap<String, Course>();
151            Map<Long, List<TeachingRequest>> id2classes = new HashMap<Long, List<TeachingRequest>>();
152            Map<String, List<TeachingRequest>> link2classes = new HashMap<String, List<TeachingRequest>>();
153            long assId = 0, reqId = 0;
154            while ((line = r.readLine()) != null) {
155                if (line.trim().isEmpty())
156                    continue;
157                String[] fields = line.split(",");
158                Long id = Long.valueOf(fields[0]);
159                String course = fields[1];
160                String section = fields[2];
161                int idx = 3;
162                int dayCode = 0;
163                idx: while (idx < fields.length && (idx == 3 || fields[idx].length() == 1)) {
164                    for (int i = 0; i < fields[idx].length(); i++) {
165                        switch (fields[idx].charAt(i)) {
166                            case 'M':
167                                dayCode += Constants.DAY_CODES[0];
168                                break;
169                            case 'T':
170                                dayCode += Constants.DAY_CODES[1];
171                                break;
172                            case 'W':
173                                dayCode += Constants.DAY_CODES[2];
174                                break;
175                            case 'R':
176                                dayCode += Constants.DAY_CODES[3];
177                                break;
178                            case 'F':
179                                dayCode += Constants.DAY_CODES[4];
180                                break;
181                            default:
182                                break idx;
183                        }
184                    }
185                    idx++;
186                }
187                int startSlot = 0;
188                if (dayCode > 0) {
189                    int time = Integer.parseInt(fields[idx++]);
190                    startSlot = 12 * (time / 100) + (time % 100) / 5;
191                }
192                String room = null;
193                if (idx < fields.length)
194                    room = fields[idx++];
195                String link = null;
196                if (idx < fields.length)
197                    link = fields[idx++];
198                int length = 12;
199                if (idx < fields.length) {
200                    int time = Integer.parseInt(fields[idx++]);
201                    int endSlot = 12 * (time / 100) + (time % 100) / 5;
202                    length = endSlot - startSlot;
203                    if (length == 10)
204                        length = 12;
205                    else if (length == 15)
206                        length = 18;
207                }
208                List<Section> sections = new ArrayList<Section>();
209                TimeLocation time = new TimeLocation(dayCode, startSlot, length, 0, 0.0, 0, null, "", null, (length == 18 ? 15 : 10));
210                sections.add(new Section(assId++, id.toString(), section, course + " " + section + " " + time.getName(true) + (room == null ? "" : " " + room), time, room, false, false));
211                Course c = courses.get(course);
212                if (c == null) {
213                    c = new Course(courses.size(), course, true, false);
214                    courses.put(course, c);
215                }
216                TeachingRequest clazz = new TeachingRequest(reqId++, 0, c, 0f, sections);
217                addVariable(clazz);
218                List<TeachingRequest> classes = id2classes.get(id);
219                if (classes == null) {
220                    classes = new ArrayList<TeachingRequest>();
221                    id2classes.put(id, classes);
222                }
223                classes.add(clazz);
224                if (link != null && !link.isEmpty()) {
225                    List<TeachingRequest> linked = link2classes.get(course + "-" + link);
226                    if (linked == null) {
227                        linked = new ArrayList<TeachingRequest>();
228                        link2classes.put(course + "-" + link, linked);
229                    }
230                    linked.add(clazz);
231                }
232            }
233
234            for (Map.Entry<Long, List<TeachingRequest>> e : id2classes.entrySet()) {
235                Long id = e.getKey();
236                List<TeachingRequest> classes = e.getValue();
237                if (classes.size() > 1) {
238                    SameInstructorConstraint sa = new SameInstructorConstraint(id, "A" + id.toString(), Constants.sPreferenceRequired);
239                    for (TeachingRequest c : classes)
240                        sa.addVariable(c);
241                    addConstraint(sa);
242                }
243            }
244            for (Map.Entry<String, List<TeachingRequest>> e : link2classes.entrySet()) {
245                String link = e.getKey();
246                List<TeachingRequest> classes = e.getValue();
247                if (classes.size() > 1) {
248                    SameLinkConstraint sa = new SameLinkConstraint(null, link, Constants.sPreferencePreferred);
249                    for (TeachingRequest c : classes)
250                        sa.addVariable(c);
251                    addConstraint(sa);
252                }
253            }
254            
255            Attribute.Type level = new Attribute.Type(0l, "Level", false, true);
256            addAttributeType(level);
257            Map<String, Attribute> code2attribute = new HashMap<String, Attribute>();
258
259            r = new BufferedReader(new FileReader(new File(dir, "level_codes.csv")));
260            String[] codes = r.readLine().split(",");
261            while ((line = r.readLine()) != null) {
262                if (line.trim().isEmpty())
263                    continue;
264                String[] fields = line.split(",");
265                String code = fields[0];
266                if (code.startsWith("\"") && code.endsWith("\""))
267                    code = code.substring(1, code.length() - 1);
268                Attribute attribute = code2attribute.get(code);
269                if (attribute == null) {
270                    attribute = new Attribute(code2attribute.size(), code, level);
271                    code2attribute.put(code, attribute);
272                }
273                for (int i = 1; i < codes.length; i++) {
274                    int pref = Integer.parseInt(fields[i]);
275                    if (pref > 0)
276                        for (TeachingRequest clazz : variables()) {
277                            if (clazz.getName().contains(codes[i]))
278                                clazz.addAttributePreference(new Preference<Attribute>(attribute, -pref));
279                        }
280                }
281            }
282            r = new BufferedReader(new FileReader(new File(dir, "hours_per_course.csv")));
283            while ((line = r.readLine()) != null) {
284                if (line.trim().isEmpty())
285                    continue;
286                String[] fields = line.split(",");
287                for (TeachingRequest clazz : variables()) {
288                    if (clazz.getName().contains(fields[0]))
289                        clazz.setLoad(Float.parseFloat(fields[1]));
290                }
291            }
292
293            String defaultCode = getProperties().getProperty("TA.DefaultLevelCode", "XXX");
294            Attribute defaultAttribute = code2attribute.get(defaultCode);
295            if (defaultAttribute == null) {
296                defaultAttribute = new Attribute(code2attribute.size(), defaultCode, level);
297                code2attribute.put(defaultCode, defaultAttribute);
298            }
299            for (TeachingRequest clazz : variables()) {
300                sLog.info("Added class " + toString(clazz));
301                if (clazz.getAttributePreferences().isEmpty()) {
302                    sLog.error("No level: " + toString(clazz));
303                    clazz.addAttributePreference(new Preference<Attribute>(defaultAttribute, -1));
304                }
305                if (clazz.getLoad() == 0.0) {
306                    sLog.error("No load: " + toString(clazz));
307                    clazz.setLoad(getProperties().getPropertyFloat("TA.DefaultLoad", 10f));
308                }
309            }
310
311            r = new BufferedReader(new FileReader(new File(dir, "students.csv")));
312            Set<String> studentIds = new HashSet<String>();
313            double studentMaxLoad = 0.0;
314            while ((line = r.readLine()) != null) {
315                if (line.trim().isEmpty())
316                    continue;
317                String[] fields = line.split(",");
318                if ("puid".equals(fields[0]))
319                    continue;
320                int idx = 0;
321                String id = fields[idx++];
322                if (!studentIds.add(id)) {
323                    sLog.error("Student " + id + " is two or more times in the file.");
324                }
325                boolean[] av = new boolean[50];
326                for (int i = 0; i < 50; i++)
327                    av[i] = "1".equals(fields[idx++]);
328                List<String> prefs = new ArrayList<String>();
329                for (int i = 0; i < 3; i++) {
330                    String p = fields[idx++].replace("Large lecture", "LEC").replace("Lecture", "LEC").replace("Recitation", "REC");
331                    if (p.startsWith("MA "))
332                        p = p.substring(3);
333                    if ("I have no preference".equals(p))
334                        continue;
335                    prefs.add(p);
336                }
337                boolean grad = "Yes".equals(fields[idx++]);
338                int b2b = Integer.parseInt(fields[idx++]);
339                float maxLoad = Float.parseFloat(fields[idx++]);
340                if (maxLoad == 0)
341                    maxLoad = getProperties().getPropertyFloat("TA.DefaultMaxLoad", 20f);
342                String code = (idx < fields.length ? fields[idx++] : null);
343                Instructor instructor = new Instructor(Long.valueOf(id.replace("-","")), id, null, grad ? Constants.sPreferenceLevelNeutral : Constants.sPreferenceLevelDiscouraged, maxLoad);
344                for (int i = 0; i < prefs.size(); i++) {
345                    String pref = prefs.get(i);
346                    if (pref.indexOf(' ') > 0) pref = pref.substring(0, pref.indexOf(' '));
347                    Course c = courses.get(pref);
348                    if (c == null) {
349                        c = new Course(courses.size(), pref, true, false);
350                        courses.put(pref, c);
351                    }
352                    instructor.addCoursePreference(new Preference<Course>(c, i == 0 ? -10 : i == 1 ? -8 : -5));
353                }
354                if (code != null) {
355                    Attribute attribute = code2attribute.get(code);
356                    if (attribute == null) {
357                        attribute = new Attribute(code2attribute.size(), code, level);
358                        code2attribute.put(code, attribute);
359                    }
360                    instructor.addAttribute(attribute);
361                }
362                if (b2b == 1)
363                    instructor.setBackToBackPreference(Constants.sPreferenceLevelPreferred);
364                else if (b2b == -1)
365                    instructor.setBackToBackPreference(Constants.sPreferenceLevelDiscouraged);
366                for (int d = 0; d < 5; d++) {
367                    int f = -1;
368                    for (int t = 0; t < 10; t++) {
369                        if (!av[10 * d + t]) {
370                            if (f < 0) f = t;
371                        } else {
372                            if (f >= 0) {
373                                instructor.addTimePreference(new Preference<TimeLocation>(new TimeLocation(Constants.DAY_CODES[d], 90 + 12 * f, (t - f) * 12, 0, 0.0, null, "", null, 0), Constants.sPreferenceLevelProhibited));
374                                f = -1;
375                            }
376                        }
377                    }
378                    if (f >= 0) {
379                        instructor.addTimePreference(new Preference<TimeLocation>(new TimeLocation(Constants.DAY_CODES[d], 90 + 12 * f, (10 - f) * 12, 0, 0.0, null, "", null, 0), Constants.sPreferenceLevelProhibited));
380                        f = -1;
381                    }
382                }
383                if (instructor.getMaxLoad() > 0) {
384                    addInstructor(instructor);
385                    sLog.info("Added student " + toString(instructor));
386                    int nrClasses = 0;
387                    for (TeachingRequest req : variables()) {
388                        if (instructor.canTeach(req) && !req.getAttributePreference(instructor).isProhibited()) {
389                            sLog.info("  -- " + toString(req) + "," + (-req.getAttributePreference(instructor).getPreferenceInt()) + "," + (-instructor.getCoursePreference(req.getCourse()).getPreference()));
390                            nrClasses++;
391                        }
392                    }
393                    if (nrClasses == 0) {
394                        sLog.info("  -- no courses available");
395                    }
396                    studentMaxLoad += instructor.getMaxLoad();
397                } else {
398                    sLog.info("Ignoring student " + instructor);
399                    if (instructor.getMaxLoad() == 0)
400                        sLog.info("  -- zero max load");
401                    else
402                        sLog.info("  -- no courses available");
403                }
404            }
405
406            double totalLoad = 0.0;
407            for (TeachingRequest clazz : variables()) {
408                if (clazz.values(getEmptyAssignment()).isEmpty())
409                    sLog.error("No values: " + toString(clazz));
410                totalLoad += clazz.getLoad();
411            }
412
413            Map<String, Double> studentLevel2load = new HashMap<String, Double>();
414            for (Instructor instructor: getInstructors()) {
415                Set<Attribute> levels = instructor.getAttributes(level);
416                String studentLevel = (levels.isEmpty() ? "null" : levels.iterator().next().getAttributeName());
417                Double load = studentLevel2load.get(studentLevel);
418                studentLevel2load.put(studentLevel, instructor.getMaxLoad() + (load == null ? 0.0 : load));
419            }
420            sLog.info("Student max loads: (total: " + sDoubleFormat.format(studentMaxLoad) + ")");
421            for (String studentLevel : new TreeSet<String>(studentLevel2load.keySet())) {
422                Double load = studentLevel2load.get(studentLevel);
423                sLog.info("  " + studentLevel + ": " + sDoubleFormat.format(load));
424            }
425            Map<String, Double> clazzLevel2load = new HashMap<String, Double>();
426            for (TeachingRequest clazz : variables()) {
427                String classLevel = null;
428                TreeSet<String> levels = new TreeSet<String>();
429                for (Preference<Attribute> ap: clazz.getAttributePreferences())
430                    levels.add(ap.getTarget().getAttributeName());
431                for (String l : levels) {
432                    classLevel = (classLevel == null ? "" : classLevel + ",") + l;
433                }
434                if (classLevel == null)
435                    classLevel = "null";
436                if (clazz.getId() < 0)
437                    classLevel = clazz.getName();
438                Double load = clazzLevel2load.get(level);
439                clazzLevel2load.put(classLevel, clazz.getLoad() + (load == null ? 0.0 : load));
440            }
441            sLog.info("Class loads: (total: " + sDoubleFormat.format(totalLoad) + ")");
442            for (String classLevel : new TreeSet<String>(clazzLevel2load.keySet())) {
443                Double load = clazzLevel2load.get(classLevel);
444                sLog.info("  " + level + ": " + sDoubleFormat.format(load));
445            }
446            return true;
447        } catch (IOException e) {
448            sLog.error("Failed to load the problem: " + e.getMessage(), e);
449            return false;
450        }
451    }
452    
453    @Override
454    protected void generateReports(File dir, Assignment<TeachingRequest, TeachingAssignment> assignment) throws IOException {
455        PrintWriter out = new PrintWriter(new File(dir, "solution-assignments.csv"));
456        out.println("Assignment Id,Course,Section,Time,Room,Link,Level,Load,Student,Availability,1st Preference,2nd Preference,3rd Preference,Graduate,Back-To-Back,Max Load,Level,Level,Preference");
457        for (TeachingRequest request : variables()) {
458            Long assId = getAssignmentId(request);
459            out.print(assId == null ? "" : assId);
460            out.print("," + request.getCourse().getCourseName());
461            Section section = request.getSections().get(0);
462            out.print("," + section.getSectionType());
463            out.print("," + section.getTimeName(true));
464            out.print(",\"" + (section.hasRoom() ? section.getRoom() : "") + "\"");
465            String link = getLink(request);
466            out.print("," + (link == null ? "" : link));
467            Map<String, Integer> levels = new HashMap<String, Integer>();
468            for (Preference<Attribute> p: request.getAttributePreferences())
469                if (p.getTarget().getType().getTypeName().equals("Level"))
470                    levels.put(p.getTarget().getAttributeName(), - p.getPreference());
471            out.print(",\"" + levels + "\"");
472            out.print("," + new DecimalFormat("0.0").format(request.getLoad()));
473            TeachingAssignment value = assignment.getValue(request);
474            if (value != null) {
475                out.print("," + toString(value.getInstructor()));
476                out.print("," + (-value.getAttributePreference()));
477                out.print("," + (value.getCoursePreference() == -10 ? "1" : value.getCoursePreference() == -8 ? "2" : value.getCoursePreference() == -5 ? "3" : value.getCoursePreference()));
478            }
479            out.println();
480        }
481        out.flush();
482        out.close();
483
484        out = new PrintWriter(new File(dir, "solution-students.csv"));
485        out.println("Student,Availability,1st Preference,2nd Preference,3rd Preference,Graduate,Back-To-Back,Max Load,Level,Assigned Load,Avg Level,Avg Preference,Back-To-Back,Diff Links,1st Assignment,2nd Assignment, 3rd Assignment");
486        for (Instructor instructor: getInstructors()) {
487            out.print(instructor.getExternalId());
488            out.print(",\"" + instructor.getAvailable() + "\"");
489            for (int i = 0; i < 3; i++) {
490                Preference<Course> p = (i < instructor.getCoursePreferences().size() ? instructor.getCoursePreferences().get(i) : null);
491                out.print("," + (p == null ? "" : p.getTarget().getCourseName()));
492            }
493            out.print("," + (instructor.getPreference() == 0 ? "Yes" : "No"));
494            out.print("," + (instructor.isBackToBackPreferred() ? "1" : instructor.isBackToBackDiscouraged() ? "-1" : "0"));
495            out.print("," + new DecimalFormat("0.0").format(instructor.getMaxLoad()));
496            String level = getLevel(instructor);
497            out.print("," + (level == null ? "" : level)); 
498            Instructor.Context context = instructor.getContext(assignment);
499            out.print("," + new DecimalFormat("0.0").format(context.getLoad()));
500            double att = 0.0, pref = 0.0;
501            for (TeachingAssignment ta : context.getAssignments()) {
502                att += Math.abs(ta.getAttributePreference());
503                pref += (ta.getCoursePreference() == -10 ? 1 : ta.getCoursePreference() == -8 ? 2 : ta.getCoursePreference() == -5 ? 3 : ta.getCoursePreference());
504            }
505            int diffLinks = countDiffLinks(context.getAssignments());
506            out.print("," + (context.getAssignments().isEmpty() ? "" : new DecimalFormat("0.0").format(att / context.getAssignments().size())));
507            out.print("," + (context.getAssignments().isEmpty() || pref == 0.0 ? "" : new DecimalFormat("0.0").format(pref / context.getAssignments().size())));
508            out.print("," + new DecimalFormat("0.0").format(100.0 * context.countBackToBackPercentage()));
509            out.print("," + (diffLinks <= 0 ? "" : diffLinks));
510            for (TeachingAssignment ta : context.getAssignments()) {
511                String link = getLink(ta.variable());
512                out.print("," + ta.variable().getCourse() + " " + ta.variable().getSections().get(0).getSectionType() + " " + ta.variable().getSections().get(0).getTime().getName(true) + (link == null ? "" : " " + link));
513            }
514            out.println();
515        }
516        out.flush();
517        out.close();
518        
519        out = new PrintWriter(new File(dir, "input-courses.csv"));
520        Set<String> levels = new TreeSet<String>();
521        for (TeachingRequest request : variables()) {
522            for (Preference<Attribute> p: request.getAttributePreferences())
523                levels.add(p.getTarget().getAttributeName());
524        }
525        out.print("Course,Type,Load");
526        for (String level: levels)
527            out.print("," + level);
528        out.println();
529        Set<String> courses = new HashSet<String>();
530        for (TeachingRequest request : variables()) {
531            if (courses.add(request.getCourse() + "," + request.getSections().get(0).getSectionType())) {
532                out.print(request.getCourse().getCourseName() + "," + request.getSections().get(0).getSectionType() + "," + request.getLoad());
533            }
534            for (String level: levels) {
535                int pref = 0;
536                for (Preference<Attribute> p: request.getAttributePreferences())
537                    if (p.getTarget().getAttributeName().equals(level)) pref = p.getPreference();
538                out.print("," + (pref == 0 ? "" : -pref));
539            }
540            out.println();
541        }
542        out.flush();
543        out.close();
544    }
545    
546    public static void main(String[] args) throws Exception {
547        DataProperties config = new DataProperties();
548        config.load(MathTest.class.getClass().getResourceAsStream("/org/cpsolver/instructor/test/math.properties"));
549        config.putAll(System.getProperties());
550        ToolBox.configureLogging();
551        
552        new MathTest(config).execute();
553    }
554}