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 Tomáš Müller<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.Variable request) {
099        for (Constraint<TeachingRequest.Variable, 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.Variable request) {
107        for (Constraint<TeachingRequest.Variable, 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.Variable 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.getRequest().getAttributePreferences())
137            levels.put(p.getTarget().getAttributeName(), - p.getPreference());
138        sb.append(",\"" + levels + "\"");
139        sb.append("," + new DecimalFormat("0.0").format(request.getRequest().getLoad()));
140        return sb.toString();
141    }
142    
143    @Override
144    protected boolean load(File dir, Assignment<TeachingRequest.Variable, 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);
214                    courses.put(course, c);
215                }
216                TeachingRequest clazz = new TeachingRequest(reqId++, 1, c, 0f, sections, Constants.sPreferenceLevelRequired, Constants.sPreferenceLevelNeutral);
217                addRequest(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.getVariables()[0]);
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.getVariables()[0]);
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            r.close();
259
260            r = new BufferedReader(new FileReader(new File(dir, "level_codes.csv")));
261            String[] codes = r.readLine().split(",");
262            while ((line = r.readLine()) != null) {
263                if (line.trim().isEmpty())
264                    continue;
265                String[] fields = line.split(",");
266                String code = fields[0];
267                if (code.startsWith("\"") && code.endsWith("\""))
268                    code = code.substring(1, code.length() - 1);
269                Attribute attribute = code2attribute.get(code);
270                if (attribute == null) {
271                    attribute = new Attribute(code2attribute.size(), code, level);
272                    code2attribute.put(code, attribute);
273                }
274                for (int i = 1; i < codes.length; i++) {
275                    int pref = Integer.parseInt(fields[i]);
276                    if (pref > 0)
277                        for (TeachingRequest clazz : getRequests()) {
278                            if (clazz.getCourse().getCourseName().contains(codes[i]))
279                                clazz.addAttributePreference(new Preference<Attribute>(attribute, -pref));
280                        }
281                }
282            }
283            r = new BufferedReader(new FileReader(new File(dir, "hours_per_course.csv")));
284            while ((line = r.readLine()) != null) {
285                if (line.trim().isEmpty())
286                    continue;
287                String[] fields = line.split(",");
288                for (TeachingRequest clazz : getRequests()) {
289                    if (clazz.getCourse().getCourseName().contains(fields[0]))
290                        clazz.setLoad(Float.parseFloat(fields[1]));
291                }
292            }
293
294            String defaultCode = getProperties().getProperty("TA.DefaultLevelCode", "XXX");
295            Attribute defaultAttribute = code2attribute.get(defaultCode);
296            if (defaultAttribute == null) {
297                defaultAttribute = new Attribute(code2attribute.size(), defaultCode, level);
298                code2attribute.put(defaultCode, defaultAttribute);
299            }
300            for (TeachingRequest.Variable clazz : variables()) {
301                sLog.info("Added class " + toString(clazz));
302                if (clazz.getRequest().getAttributePreferences().isEmpty()) {
303                    sLog.error("No level: " + toString(clazz));
304                    clazz.getRequest().addAttributePreference(new Preference<Attribute>(defaultAttribute, -1));
305                }
306                if (clazz.getRequest().getLoad() == 0.0) {
307                    sLog.error("No load: " + toString(clazz));
308                    clazz.getRequest().setLoad(getProperties().getPropertyFloat("TA.DefaultLoad", 10f));
309                }
310            }
311
312            r = new BufferedReader(new FileReader(new File(dir, "students.csv")));
313            Set<String> studentIds = new HashSet<String>();
314            double studentMaxLoad = 0.0;
315            while ((line = r.readLine()) != null) {
316                if (line.trim().isEmpty())
317                    continue;
318                String[] fields = line.split(",");
319                if ("puid".equals(fields[0]))
320                    continue;
321                int idx = 0;
322                String id = fields[idx++];
323                if (!studentIds.add(id)) {
324                    sLog.error("Student " + id + " is two or more times in the file.");
325                }
326                boolean[] av = new boolean[50];
327                for (int i = 0; i < 50; i++)
328                    av[i] = "1".equals(fields[idx++]);
329                List<String> prefs = new ArrayList<String>();
330                for (int i = 0; i < 3; i++) {
331                    String p = fields[idx++].replace("Large lecture", "LEC").replace("Lecture", "LEC").replace("Recitation", "REC");
332                    if (p.startsWith("MA "))
333                        p = p.substring(3);
334                    if ("I have no preference".equals(p))
335                        continue;
336                    prefs.add(p);
337                }
338                boolean grad = "Yes".equals(fields[idx++]);
339                int b2b = Integer.parseInt(fields[idx++]);
340                float maxLoad = Float.parseFloat(fields[idx++]);
341                if (maxLoad == 0)
342                    maxLoad = getProperties().getPropertyFloat("TA.DefaultMaxLoad", 20f);
343                String code = (idx < fields.length ? fields[idx++] : null);
344                Instructor instructor = new Instructor(Long.valueOf(id.replace("-","")), id, null, grad ? Constants.sPreferenceLevelNeutral : Constants.sPreferenceLevelDiscouraged, maxLoad);
345                for (int i = 0; i < prefs.size(); i++) {
346                    String pref = prefs.get(i);
347                    if (pref.indexOf(' ') > 0) pref = pref.substring(0, pref.indexOf(' '));
348                    Course c = courses.get(pref);
349                    if (c == null) {
350                        c = new Course(courses.size(), pref);
351                        courses.put(pref, c);
352                    }
353                    instructor.addCoursePreference(new Preference<Course>(c, i == 0 ? -10 : i == 1 ? -8 : -5));
354                }
355                if (code != null) {
356                    Attribute attribute = code2attribute.get(code);
357                    if (attribute == null) {
358                        attribute = new Attribute(code2attribute.size(), code, level);
359                        code2attribute.put(code, attribute);
360                    }
361                    instructor.addAttribute(attribute);
362                }
363                if (b2b == 1)
364                    instructor.setBackToBackPreference(Constants.sPreferenceLevelPreferred);
365                else if (b2b == -1)
366                    instructor.setBackToBackPreference(Constants.sPreferenceLevelDiscouraged);
367                for (int d = 0; d < 5; d++) {
368                    int f = -1;
369                    for (int t = 0; t < 10; t++) {
370                        if (!av[10 * d + t]) {
371                            if (f < 0) f = t;
372                        } else {
373                            if (f >= 0) {
374                                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));
375                                f = -1;
376                            }
377                        }
378                    }
379                    if (f >= 0) {
380                        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));
381                        f = -1;
382                    }
383                }
384                if (instructor.getMaxLoad() > 0) {
385                    addInstructor(instructor);
386                    sLog.info("Added student " + toString(instructor));
387                    int nrClasses = 0;
388                    for (TeachingRequest.Variable req : variables()) {
389                        if (instructor.canTeach(req.getRequest()) && !req.getRequest().getAttributePreference(instructor).isProhibited()) {
390                            sLog.info("  -- " + toString(req) + "," + (-req.getRequest().getAttributePreference(instructor).getPreferenceInt()) + "," + (-instructor.getCoursePreference(req.getCourse()).getPreference()));
391                            nrClasses++;
392                        }
393                    }
394                    if (nrClasses == 0) {
395                        sLog.info("  -- no courses available");
396                    }
397                    studentMaxLoad += instructor.getMaxLoad();
398                } else {
399                    sLog.info("Ignoring student " + instructor);
400                    if (instructor.getMaxLoad() == 0)
401                        sLog.info("  -- zero max load");
402                    else
403                        sLog.info("  -- no courses available");
404                }
405            }
406            r.close();
407
408            double totalLoad = 0.0;
409            for (TeachingRequest.Variable clazz : variables()) {
410                if (clazz.values(getEmptyAssignment()).isEmpty())
411                    sLog.error("No values: " + toString(clazz));
412                totalLoad += clazz.getRequest().getLoad();
413            }
414
415            Map<String, Double> studentLevel2load = new HashMap<String, Double>();
416            for (Instructor instructor: getInstructors()) {
417                Set<Attribute> levels = instructor.getAttributes(level);
418                String studentLevel = (levels.isEmpty() ? "null" : levels.iterator().next().getAttributeName());
419                Double load = studentLevel2load.get(studentLevel);
420                studentLevel2load.put(studentLevel, instructor.getMaxLoad() + (load == null ? 0.0 : load));
421            }
422            sLog.info("Student max loads: (total: " + sDoubleFormat.format(studentMaxLoad) + ")");
423            for (String studentLevel : new TreeSet<String>(studentLevel2load.keySet())) {
424                Double load = studentLevel2load.get(studentLevel);
425                sLog.info("  " + studentLevel + ": " + sDoubleFormat.format(load));
426            }
427            Map<String, Double> clazzLevel2load = new HashMap<String, Double>();
428            for (TeachingRequest.Variable clazz : variables()) {
429                String classLevel = null;
430                TreeSet<String> levels = new TreeSet<String>();
431                for (Preference<Attribute> ap: clazz.getRequest().getAttributePreferences())
432                    levels.add(ap.getTarget().getAttributeName());
433                for (String l : levels) {
434                    classLevel = (classLevel == null ? "" : classLevel + ",") + l;
435                }
436                if (classLevel == null)
437                    classLevel = "null";
438                if (clazz.getId() < 0)
439                    classLevel = clazz.getName();
440                Double load = clazzLevel2load.get(classLevel);
441                clazzLevel2load.put(classLevel, clazz.getRequest().getLoad() + (load == null ? 0.0 : load));
442            }
443            sLog.info("Class loads: (total: " + sDoubleFormat.format(totalLoad) + ")");
444            for (String classLevel : new TreeSet<String>(clazzLevel2load.keySet())) {
445                Double load = clazzLevel2load.get(classLevel);
446                sLog.info("  " + level + ": " + sDoubleFormat.format(load));
447            }
448            return true;
449        } catch (IOException e) {
450            sLog.error("Failed to load the problem: " + e.getMessage(), e);
451            return false;
452        }
453    }
454    
455    @Override
456    protected void generateReports(File dir, Assignment<TeachingRequest.Variable, TeachingAssignment> assignment) throws IOException {
457        PrintWriter out = new PrintWriter(new File(dir, "solution-assignments.csv"));
458        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");
459        for (TeachingRequest.Variable request : variables()) {
460            Long assId = getAssignmentId(request);
461            out.print(assId == null ? "" : assId);
462            out.print("," + request.getCourse().getCourseName());
463            Section section = request.getSections().get(0);
464            out.print("," + section.getSectionType());
465            out.print("," + section.getTimeName(true));
466            out.print(",\"" + (section.hasRoom() ? section.getRoom() : "") + "\"");
467            String link = getLink(request);
468            out.print("," + (link == null ? "" : link));
469            Map<String, Integer> levels = new HashMap<String, Integer>();
470            for (Preference<Attribute> p: request.getRequest().getAttributePreferences())
471                if (p.getTarget().getType().getTypeName().equals("Level"))
472                    levels.put(p.getTarget().getAttributeName(), - p.getPreference());
473            out.print(",\"" + levels + "\"");
474            out.print("," + new DecimalFormat("0.0").format(request.getRequest().getLoad()));
475            TeachingAssignment value = assignment.getValue(request);
476            if (value != null) {
477                out.print("," + toString(value.getInstructor()));
478                out.print("," + (-value.getAttributePreference()));
479                out.print("," + (value.getCoursePreference() == -10 ? "1" : value.getCoursePreference() == -8 ? "2" : value.getCoursePreference() == -5 ? "3" : value.getCoursePreference()));
480            }
481            out.println();
482        }
483        out.flush();
484        out.close();
485
486        out = new PrintWriter(new File(dir, "solution-students.csv"));
487        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");
488        for (Instructor instructor: getInstructors()) {
489            out.print(instructor.getExternalId());
490            out.print(",\"" + instructor.getAvailable() + "\"");
491            for (int i = 0; i < 3; i++) {
492                Preference<Course> p = (i < instructor.getCoursePreferences().size() ? instructor.getCoursePreferences().get(i) : null);
493                out.print("," + (p == null ? "" : p.getTarget().getCourseName()));
494            }
495            out.print("," + (instructor.getPreference() == 0 ? "Yes" : "No"));
496            out.print("," + (instructor.isBackToBackPreferred() ? "1" : instructor.isBackToBackDiscouraged() ? "-1" : "0"));
497            out.print("," + new DecimalFormat("0.0").format(instructor.getMaxLoad()));
498            String level = getLevel(instructor);
499            out.print("," + (level == null ? "" : level)); 
500            Instructor.Context context = instructor.getContext(assignment);
501            out.print("," + new DecimalFormat("0.0").format(context.getLoad()));
502            double att = 0.0, pref = 0.0;
503            for (TeachingAssignment ta : context.getAssignments()) {
504                att += Math.abs(ta.getAttributePreference());
505                pref += (ta.getCoursePreference() == -10 ? 1 : ta.getCoursePreference() == -8 ? 2 : ta.getCoursePreference() == -5 ? 3 : ta.getCoursePreference());
506            }
507            int diffLinks = countDiffLinks(context.getAssignments());
508            out.print("," + (context.getAssignments().isEmpty() ? "" : new DecimalFormat("0.0").format(att / context.getAssignments().size())));
509            out.print("," + (context.getAssignments().isEmpty() || pref == 0.0 ? "" : new DecimalFormat("0.0").format(pref / context.getAssignments().size())));
510            out.print("," + new DecimalFormat("0.0").format(100.0 * context.countBackToBackPercentage()));
511            out.print("," + (diffLinks <= 0 ? "" : diffLinks));
512            for (TeachingAssignment ta : context.getAssignments()) {
513                String link = getLink(ta.variable());
514                out.print("," + ta.variable().getCourse() + " " + ta.variable().getSections().get(0).getSectionType() + " " + ta.variable().getSections().get(0).getTime().getName(true) + (link == null ? "" : " " + link));
515            }
516            out.println();
517        }
518        out.flush();
519        out.close();
520        
521        out = new PrintWriter(new File(dir, "input-courses.csv"));
522        Set<String> levels = new TreeSet<String>();
523        for (TeachingRequest.Variable request : variables()) {
524            for (Preference<Attribute> p: request.getRequest().getAttributePreferences())
525                levels.add(p.getTarget().getAttributeName());
526        }
527        out.print("Course,Type,Load");
528        for (String level: levels)
529            out.print("," + level);
530        out.println();
531        Set<String> courses = new HashSet<String>();
532        for (TeachingRequest.Variable request : variables()) {
533            if (courses.add(request.getCourse() + "," + request.getSections().get(0).getSectionType())) {
534                out.print(request.getCourse().getCourseName() + "," + request.getSections().get(0).getSectionType() + "," + request.getRequest().getLoad());
535                for (String level: levels) {
536                    int pref = 0;
537                    for (Preference<Attribute> p: request.getRequest().getAttributePreferences())
538                        if (p.getTarget().getAttributeName().equals(level)) pref = p.getPreference();
539                    out.print("," + (pref == 0 ? "" : -pref));
540                }
541                out.println();
542            }
543        }
544        out.flush();
545        out.close();
546    }
547    
548    public static void main(String[] args) throws Exception {
549        DataProperties config = new DataProperties();
550        config.load(MathTest.class.getClass().getResourceAsStream("/org/cpsolver/instructor/test/math.properties"));
551        config.putAll(System.getProperties());
552        ToolBox.configureLogging();
553        
554        new MathTest(config).execute();
555    }
556}