001package org.cpsolver.exam;
002
003import java.io.File;
004import java.io.IOException;
005import java.io.PrintWriter;
006import java.util.ArrayList;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Locale;
011import java.util.Map;
012import java.util.Set;
013
014
015import org.cpsolver.exam.model.Exam;
016import org.cpsolver.exam.model.ExamModel;
017import org.cpsolver.exam.model.ExamPlacement;
018import org.cpsolver.exam.model.ExamRoom;
019import org.cpsolver.exam.model.ExamRoomPlacement;
020import org.cpsolver.exam.model.ExamStudent;
021import org.cpsolver.ifs.assignment.Assignment;
022import org.cpsolver.ifs.assignment.DefaultSingleAssignment;
023import org.cpsolver.ifs.util.DataProperties;
024import org.cpsolver.ifs.util.Progress;
025import org.cpsolver.ifs.util.ToolBox;
026import org.dom4j.io.SAXReader;
027
028/**
029 * A simple program that prints a few statistics about the given examination problem in the format of the MISTA 2013 paper
030 * (entitled Real-life Examination Timetabling).
031 * It outputs data for the Table 1 (characteristics of the data sets) and Table 2 (number of rooms and exams of a certain size). 
032 * <br>
033 * Usage:
034 * <pre><code>java -cp cpsolver-all-1.2.jar org.cpsolver.exam.MistaTables problem1.xml problem2.xml ...</code></pre>
035 * <br>
036 * 
037 * @author  Tomáš Müller
038 * @version ExamTT 1.3 (Examination Timetabling)<br>
039 *          Copyright (C) 2008 - 2014 Tomáš Müller<br>
040 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
041 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
042 * <br>
043 *          This library is free software; you can redistribute it and/or modify
044 *          it under the terms of the GNU Lesser General Public License as
045 *          published by the Free Software Foundation; either version 3 of the
046 *          License, or (at your option) any later version. <br>
047 * <br>
048 *          This library is distributed in the hope that it will be useful, but
049 *          WITHOUT ANY WARRANTY; without even the implied warranty of
050 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
051 *          Lesser General Public License for more details. <br>
052 * <br>
053 *          You should have received a copy of the GNU Lesser General Public
054 *          License along with this library; if not see
055 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
056 */
057public class MistaTables {
058    private static org.apache.logging.log4j.Logger sLog = org.apache.logging.log4j.LogManager.getLogger(MistaTables.class);
059    private static java.text.DecimalFormat sNF = new java.text.DecimalFormat("###,##0", new java.text.DecimalFormatSymbols(Locale.US));
060    private static java.text.DecimalFormat sDF = new java.text.DecimalFormat("###,##0.000", new java.text.DecimalFormatSymbols(Locale.US));
061    
062    public static void main(String[] args) {
063        try {
064            ToolBox.configureLogging();
065            DataProperties config = new DataProperties();
066            
067            Table[] tables = new Table[] { new Problems(), new Rooms() };
068            
069            for (int i = 0; i < args.length; i++) {
070                File file = new File(args[i]);
071                sLog.info("Loading " + file);
072                ExamModel model = new ExamModel(config);
073                Assignment<Exam, ExamPlacement> assignment = new DefaultSingleAssignment<Exam, ExamPlacement>();
074                model.load(new SAXReader().read(file), assignment);
075                
076                String name = file.getName();
077                if (name.contains("."))
078                    name = name.substring(0, name.indexOf('.'));
079                
080                for (Table table: tables)
081                    table.add(name, model);
082                
083                Progress.removeInstance(model);
084            }
085            
086            sLog.info("Saving tables...");
087            File output = new File("tables"); output.mkdir();
088            for (Table table: tables)
089                table.save(output);
090            
091            sLog.info("All done.");
092        } catch (Exception e) {
093            sLog.error(e.getMessage(), e);
094        }
095    }
096    
097    public static class Counter {
098        private double iTotal = 0.0, iMin = 0.0, iMax = 0.0, iTotalSquare = 0.0;
099        private int iCount = 0;
100        
101        public Counter() {
102        }
103        
104        public void inc(double value) {
105                if (iCount == 0) {
106                        iTotal = value;
107                        iMin = value;
108                        iMax = value;
109                        iTotalSquare = value * value;
110                } else {
111                        iTotal += value;
112                        iMin = Math.min(iMin, value);
113                        iMax = Math.max(iMax, value);
114                        iTotalSquare += value * value;
115                }
116                iCount ++;
117        }
118        
119        public int count() { return iCount; }
120        public double sum() { return iTotal; }
121        public double min() { return iMin; }
122        public double max() { return iMax; }
123        public double rms() { return (iCount == 0 ? 0.0 : Math.sqrt(iTotalSquare / iCount) - Math.abs(avg())); }
124        public double avg() { return (iCount == 0 ? 0.0 : iTotal / iCount); }
125
126        @Override
127        public String toString() {
128                return sDF.format(sum()) +
129                " (min: " + sDF.format(min()) +
130                ", max: " + sDF.format(max()) +
131                ", avg: " + sDF.format(avg()) +
132                ", rms: " + sDF.format(rms()) +
133                ", cnt: " + count() + ")";
134        }
135    }
136    
137    public static abstract class Table {
138        private List<String> iProblems = new ArrayList<String>();
139        private List<String> iProperties = new ArrayList<String>();
140        private Map<String, Map<String, String>> iData = new HashMap<String, Map<String, String>>();
141        
142        public abstract void add(String problem, ExamModel model);
143        
144        
145        protected void add(String problem, String property, int value) {
146            add(problem, property, sNF.format(value));
147        }
148        
149        protected void add(String problem, String property, double value) {
150            add(problem, property, sDF.format(value));
151        }
152        
153        protected void add(String problem, String property, Counter value) {
154            add(problem, property, sDF.format(value.avg()) + " ± " + sDF.format(value.rms()));
155        }
156
157        protected void add(String problem, String property, String value) {
158            if (!iProblems.contains(problem)) iProblems.add(problem);
159            if (!iProperties.contains(property)) iProperties.add(property);
160            Map<String, String> table = iData.get(problem);
161            if (table == null) {
162                table = new HashMap<String, String>();
163                iData.put(problem, table);
164            }
165            table.put(property, value);
166        }
167        
168        public void save(File folder) throws IOException {
169            PrintWriter pw = new PrintWriter(new File(folder, getClass().getSimpleName() + ".csv"));
170            
171            pw.print("Problem");
172            for (String problem: iProblems) pw.print(",\"" + problem + "\"");
173            pw.println();
174            
175            for (String property: iProperties) {
176                pw.print("\"" + property + "\"");
177                for (String problem: iProblems) {
178                    String value = iData.get(problem).get(property);
179                    pw.print("," + (value == null ? "" : "\"" + value + "\""));
180                }
181                pw.println();
182            }
183
184            pw.flush(); pw.close();
185        }
186    }
187    
188    public static class Problems extends Table {
189        @Override
190        public void add(String problem, ExamModel model) {
191            int enrollments = 0;
192            for (ExamStudent student: model.getStudents())
193                enrollments += student.variables().size();
194            
195            int examSeating = 0;
196            int examsFixedInTime = 0, examsFixedInRoom = 0, examsLarge = 0, examsToSplit = 0, examsWithOriginalRoom = 0;
197            Counter avgPeriods = new Counter(), avgRooms = new Counter(), avgBigRooms = new Counter();
198            double density = 0;
199            
200            for (Exam exam: model.variables()) {
201                if (exam.hasAltSeating()) examSeating ++;
202
203                if (exam.getPeriodPlacements().size() <= 2)
204                    examsFixedInTime ++;
205                if (exam.getRoomPlacements().size() <= 2)
206                    examsFixedInRoom ++;
207                
208                for (ExamRoomPlacement room: exam.getRoomPlacements()) {
209                    if (room.getPenalty() < -2) { examsWithOriginalRoom ++; break; }
210                }
211                
212                int bigEnoughRooms = 0;
213                for (ExamRoomPlacement room: exam.getRoomPlacements()) {
214                    if (room.getSize(exam.hasAltSeating()) >= exam.getSize()) bigEnoughRooms ++;
215                }
216                
217                if (bigEnoughRooms == 0)
218                    examsToSplit ++;
219                
220                if (exam.getSize() >= 600)
221                    examsLarge ++;
222                
223                avgPeriods.inc(exam.getPeriodPlacements().size());
224                avgRooms.inc(exam.getRoomPlacements().size());
225                avgBigRooms.inc(bigEnoughRooms);
226                
227                density += exam.nrStudentCorrelatedExams();
228            }
229            
230            add(problem, "Exams", model.variables().size());
231            add(problem, "   with exam seating", examSeating);
232            add(problem, "Students", model.getStudents().size());
233            add(problem, "Enrollments", enrollments);
234            add(problem, "Distribution constraints", model.getDistributionConstraints().size());
235            
236            add(problem, "Exams fixed in time", examsFixedInTime);
237            add(problem, "Exams fixed in room", examsFixedInRoom);
238            add(problem, "Large exams (600+)", examsLarge);
239            add(problem, "Exams needing a room split", examsToSplit);
240            add(problem, "Exams with original room", examsWithOriginalRoom);
241            add(problem, "Density", sDF.format(100.0 * density / (model.variables().size() * (model.variables().size() - 1))) + "%");
242            
243            add(problem, "Average periods", avgPeriods);
244            add(problem, "Average rooms", avgRooms);
245            add(problem, "   that are big enough", avgBigRooms);
246        }
247    }
248    
249    public static class Rooms extends Table {
250        @Override
251        public void add(String problem, ExamModel model) {
252            int[] sizes = new int[] { 0, 100, 200, 400, 600 };
253            int[] nrRooms = new int[] { 0, 0, 0, 0, 0 }, nrRoomsAlt = new int[] { 0, 0, 0, 0, 0 };
254            int[] nrExams = new int[] { 0, 0, 0, 0, 0 }, nrExamsAlt = new int[] { 0, 0, 0, 0, 0 };
255            double[] density = new double[] { 0, 0, 0, 0, 0 };
256            
257            Set<ExamRoom> rooms = new HashSet<ExamRoom>();
258            for (Exam exam: model.variables()) {
259                for (ExamRoomPlacement room: exam.getRoomPlacements()) {
260                    if (rooms.add(room.getRoom())) {
261                        for (int i = 0; i < sizes.length; i++) {
262                            if (room.getRoom().getSize() >= sizes[i])
263                                nrRooms[i] ++;
264                            if (room.getRoom().getAltSize() >= sizes[i])
265                                nrRoomsAlt[i] ++;
266                        }
267                    }
268                }
269
270                for (int i = 0; i < sizes.length; i++) {
271                    if (exam.getSize() >= sizes[i]) {
272                        nrExams[i] ++;
273                        if (exam.hasAltSeating())
274                            nrExamsAlt[i] ++;
275                        for (Exam x: exam.getStudentCorrelatedExams())
276                            if (x.getSize() >= sizes[i])
277                                density[i] ++;
278                    }
279                }
280            }
281            
282            for (int i = 0; i < sizes.length; i++) {
283                add(problem, "Rooms" + (sizes[i] == 0 ? "" : " (≥ " + sizes[i] + " seats)"), sNF.format(nrRooms[i]) + (sizes[i] == 0 ? "" : " (" + sNF.format(nrRoomsAlt[i]) + ")"));
284            }
285            for (int i = 0; i < sizes.length; i++) {
286                add(problem, "Exams" + (sizes[i] == 0 ? "" : " (≥ " + sizes[i] + " seats)"), sNF.format(nrExams[i]) + (sizes[i] == 0 ? "" : " (" + sNF.format(nrExamsAlt[i]) + ")"));
287            }
288            for (int i = 0; i < sizes.length; i++) {
289                add(problem, "Density" + (sizes[i] == 0 ? "" : " (≥ " + sizes[i] + " seats)"), sDF.format(100.0 * density[i] / (nrExams[i] * (nrExams[i] - 1))) + "%");
290            }
291        }
292    }
293
294}