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