View Javadoc

1   /*
2    * Copyright 2006 - 2012 Christina Bohk and Roland Ewald
3    *  
4    * Licensed under the Apache License, Version 2.0 (the "License"); 
5    * you may not use this file except in compliance with the License. 
6    * You may obtain a copy of the License at 
7    *  
8    *  http://www.apache.org/licenses/LICENSE-2.0
9    *  
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License. 
15   */
16  package p3j.experiment.results;
17  
18  import james.SimSystem;
19  import james.core.util.misc.Strings;
20  import james.core.util.misc.Triple;
21  
22  import java.io.BufferedInputStream;
23  import java.io.BufferedOutputStream;
24  import java.io.File;
25  import java.io.FileOutputStream;
26  import java.io.FileWriter;
27  import java.io.IOException;
28  import java.io.Writer;
29  import java.text.SimpleDateFormat;
30  import java.util.ArrayList;
31  import java.util.Calendar;
32  import java.util.Collections;
33  import java.util.Comparator;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.logging.Level;
38  
39  import p3j.database.DatabaseFactory;
40  import p3j.database.IProjectionResultsIterator;
41  import p3j.experiment.results.filters.IResultFilter;
42  import p3j.gui.P3J;
43  import p3j.misc.IProgressObserver;
44  import p3j.misc.gui.GUI;
45  import p3j.misc.math.Matrix2D;
46  import p3j.pppm.ProjectionModel;
47  import freemarker.template.Configuration;
48  import freemarker.template.DefaultObjectWrapper;
49  import freemarker.template.Template;
50  import freemarker.template.TemplateException;
51  
52  /**
53   * Class responsible for exporting results. There are three modes:
54   * 
55   * Create report: aggregates all data to be displayed in the result report.
56   * 
57   * Export aggregated data: exports various data aggregations, does not copy or
58   * generate report-related files.
59   * 
60   * Export data: exports all raw data, to be analyzed with other tools.
61   * 
62   * TODO: Remove hard-coded density-plot years here and in plotting.R!
63   * 
64   * @author Christina Bohk
65   * @author Roland Ewald
66   */
67  public class ResultExport {
68  
69    /** The name of the R library required for report generation. */
70    public static final String R_LIBRARY = "plotting.R";
71  
72    /** The template directory. */
73    public static final String TEMPLATE_DIRECTORY = "report_template";
74  
75    /** The buffer size for copying the plotting library. */
76    private static final int COPY_BUFFER_SIZE = 1024;
77  
78    /** The result filter to be used. */
79    private final IResultFilter resultFilter;
80  
81    /** The year offsets to consider for plotting probability densities. */
82    private final int[] yearOffsetToConsider;
83  
84    /** The number of years to be projected. */
85    private final int numOfYears;
86  
87    /** The number of trials. */
88    private final int numOfTrials;
89  
90    /** The target directory. */
91    private final File targetDir;
92  
93    /** Reference to the projection of which the results shall be exported. */
94    private final ProjectionModel projection;
95  
96    /**
97     * The current quantiles to be used. TODO: This should be parameterisable via
98     * the UI.
99     */
100   static final double[] QUANTILES = new double[] { 0, .025, .05, .1, .5, .9,
101       0.95, 0.975, 1 };
102 
103   /** The number of sub-plots to be put in a density plot. */
104   static final int DENSITY_PLOT_NUM_SUBPLOTS = 9;
105 
106   /** The offset between jump-off year and first year to be plotted. */
107   static final int DENSITY_PLOT_FIRST_OFFSET = 3;
108 
109   /** The regular offset between years to be plotted. */
110   static final int DENSITY_PLOT_REGULAR_OFFSET = 5;
111 
112   /**
113    * Instantiates a new result export.
114    * 
115    * @param projectionModel
116    *          the projection model
117    * @param targetDirectory
118    *          the target directory
119    * @param resultFilter
120    *          the result filter to be used
121    */
122   public ResultExport(ProjectionModel projectionModel, File targetDirectory,
123       IResultFilter resultFilter) {
124 
125     projection = projectionModel;
126     targetDir = targetDirectory;
127     this.resultFilter = resultFilter;
128 
129     // Check whether target directory exists
130     if (!targetDir.exists() && !targetDir.mkdir()) {
131       throw new IllegalArgumentException("Could not create directory: "
132           + targetDir.getAbsolutePath());
133     }
134 
135     // Consider each year for aggregation
136     yearOffsetToConsider = new int[projectionModel.getYears() - 1];
137     for (int i = 0; i < yearOffsetToConsider.length; i++) {
138       yearOffsetToConsider[i] = 1;
139     }
140 
141     // Store the number overall number of years and trials
142     numOfYears = projectionModel.getYears();
143     numOfTrials = DatabaseFactory.getDatabaseSingleton()
144         .getAllResults(projection).size();
145   }
146 
147   /**
148    * Exports all results.
149    * 
150    * @throws IOException
151    *           if export fails
152    */
153   public void exportAllResults() throws IOException {
154     File dataDirectory = initializeSubDirectory("data");
155     exportData(dataDirectory);
156     GUI.printMessage(P3J.getInstance(), "Results Export Finished",
157         "Results export is finished.");
158   }
159 
160   /**
161    * Exports aggregated data.
162    * 
163    * @param progress
164    *          the progress observer
165    * @throws IOException
166    *           if data storage failed
167    */
168   public void exportAggregatedResults(IProgressObserver progress)
169       throws IOException {
170     File aggregatedDirectory = initializeSubDirectory("aggregated_data");
171     aggregateData(aggregatedDirectory,
172         (new ResultAggregation(projection.getGenerations(), numOfYears))
173             .getSelectorsForAggregatedDataExport(), progress);
174     if (progress.isCancelled())
175       return;
176     GUI.printMessage(P3J.getInstance(), "Aggregated Results Export Finished",
177         "Aggregated results export is finished.");
178   }
179 
180   /**
181    * Creates result report displaying aggregated results.
182    * 
183    * @param progress
184    *          the progress observation mechanism
185    * @throws IOException
186    *           if aggregation of data fails
187    * @throws TemplateException
188    *           if processing of report template fails
189    */
190   public void createResultReport(IProgressObserver progress)
191       throws IOException, TemplateException {
192     progress.addWaypoints(5);
193     progress.incrementProgress("Initializing sub-directory...");
194     File aggregatedDirectory = initializeSubDirectory("aggregated");
195     progress.incrementProgress("Retrieving data selectors...");
196     IAggregationSelector[] selectors = (new ResultAggregation(
197         projection.getGenerations(), numOfYears)).getSelectorsForReport();
198     progress.incrementProgress("Data aggregation...");
199     Map<String, Object> aggregationInfoMap = aggregateData(aggregatedDirectory,
200         selectors, progress);
201     if (aggregationInfoMap.isEmpty()) {
202       GUI.printMessage(P3J.getInstance(), "No results to report",
203           "No results could be reported, as none matched you filter.");
204       return;
205     }
206     if (progress.isCancelled())
207       return;
208     progress.incrementProgress("Coppy plotting library...");
209     copyPlottingLib();
210     progress.incrementProgress("Creating Sweave file...");
211     createSweaveFile(aggregationInfoMap);
212     GUI.printMessage(
213         P3J.getInstance(),
214         "Report Generation Finished",
215         "Result report generation is finished. Now run \"Sweave('report.Rtex')\" in R, then use LaTeX to process the 'report.tex' file.'");
216   }
217 
218   /**
219    * Stores assumption encoding.
220    * 
221    * @param projection
222    *          the projection
223    * @param targetDir
224    *          the target directory
225    * 
226    * @return the corresponding parameter assumption encoder
227    * 
228    * @throws IOException
229    *           Signals that an I/O exception has occurred.
230    */
231   private ParameterAssumptionEncoder storeAssumptionEncoding(
232       ProjectionModel projection, File targetDir) throws IOException {
233     ParameterAssumptionEncoder assumptionEncoder = new ParameterAssumptionEncoder(
234         projection);
235     assumptionEncoder.writeMappingSummary(targetDir);
236     return assumptionEncoder;
237   }
238 
239   /**
240    * Aggregate data.
241    * 
242    * @param destinationDir
243    *          the destination directory
244    * @param selectors
245    *          the selectors
246    * @param progress
247    *          the progress observer
248    * @return a map containing the data to parse into the report template
249    * @throws IOException
250    *           if data storage failed
251    */
252   private Map<String, Object> aggregateData(File destinationDir,
253       IAggregationSelector[] selectors, IProgressObserver progress)
254       throws IOException {
255 
256     // Create assumption encoder
257     ParameterAssumptionEncoder assumptionEncoder = storeAssumptionEncoding(
258         projection, destinationDir);
259     Map<String, Object> results = new HashMap<String, Object>();
260 
261     // TODO: Implement multi-threaded report-generation.
262     // Each selector should have its own thread.
263 
264     initializeSelectors(projection, selectors);
265     List<Triple<Integer, Double, int[]>> trialAssumptions = analyzeResults(
266         projection, selectors, assumptionEncoder, progress);
267     if (progress.isCancelled() || trialAssumptions.isEmpty())
268       return results;
269 
270     storeData(destinationDir, selectors, trialAssumptions);
271 
272     results.put(
273         "densityYears",
274         createDensityYearList(projection.getJumpOffYear(),
275             DENSITY_PLOT_FIRST_OFFSET, DENSITY_PLOT_REGULAR_OFFSET));
276     results.put("finalYearIndex", numOfYears - 1);
277     results.put("allYears",
278         createFullYearList(projection.getJumpOffYear(), numOfYears - 1));
279 
280     return results;
281   }
282 
283   /**
284    * Store data.
285    * 
286    * @param destinationDir
287    *          the destination dir
288    * @param selectors
289    *          the selectors
290    * @param trialAssumptions
291    *          the trial assumptions
292    * 
293    * @throws IOException
294    *           Signals that an I/O exception has occurred.
295    */
296   private void storeData(File destinationDir, IAggregationSelector[] selectors,
297       List<Triple<Integer, Double, int[]>> trialAssumptions) throws IOException {
298     List<Integer> indexOrder = sortAndStore(destinationDir, trialAssumptions);
299     for (IAggregationSelector selector : selectors) {
300       selector.finish(destinationDir, indexOrder, this);
301     }
302   }
303 
304   /**
305    * Sorts assumption sets of trial by probability (descending order) and stores
306    * them into file.
307    * 
308    * @param destinationDir
309    *          the destination directory
310    * @param trialAssumptions
311    *          the trial assumptions
312    * 
313    * @return the list of trial indices, which defines the reordering
314    * 
315    * @throws IOException
316    *           Signals that an I/O exception has occurred.
317    */
318   private List<Integer> sortAndStore(File destinationDir,
319       List<Triple<Integer, Double, int[]>> trialAssumptions) throws IOException {
320 
321     // Sort in *descending* order
322     Collections.sort(trialAssumptions,
323         new Comparator<Triple<Integer, Double, int[]>>() {
324           @Override
325           public int compare(Triple<Integer, Double, int[]> o1,
326               Triple<Integer, Double, int[]> o2) {
327             return -1 * o1.getB().compareTo(o2.getB());
328           }
329         });
330 
331     // Write file
332     FileWriter fw = new FileWriter(destinationDir.getAbsolutePath()
333         + File.separatorChar + "assumptions_sorted.txt");
334     List<Integer> resultingOrder = new ArrayList<Integer>();
335     for (Triple<Integer, Double, int[]> trialAssumption : trialAssumptions) {
336       resultingOrder.add(trialAssumption.getA());
337       fw.append(trialAssumption.getA() + "\t" + trialAssumption.getB() + "\t");
338       for (int elem : trialAssumption.getC()) {
339         fw.append(elem + "\t");
340       }
341       fw.append("\n");
342     }
343     fw.close();
344 
345     return resultingOrder;
346   }
347 
348   /**
349    * Analyzes and filters results. Applies all selectors to the results that are
350    * not filtered.
351    * 
352    * @param projection
353    *          the projection
354    * @param selectors
355    *          the selectors
356    * @param assumptionEncoder
357    *          the assumption encoder
358    * @param progress
359    *          the progress observer
360    * @return the assumptions of the trials - triples of (#trial, probability,
361    *         encoded assumptions) - in correct order
362    */
363   private List<Triple<Integer, Double, int[]>> analyzeResults(
364       ProjectionModel projection, IAggregationSelector[] selectors,
365       ParameterAssumptionEncoder assumptionEncoder, IProgressObserver progress) {
366 
367     // This does not read out all results completely, as they are accessed
368     // lazily...
369     progress.addWaypoints(DatabaseFactory.getDatabaseSingleton()
370         .getAllResults(projection).size());
371 
372     // ... using an iterator
373     IProjectionResultsIterator resultsIterator = DatabaseFactory
374         .getDatabaseSingleton().getResultIterator(projection);
375 
376     ResultsOfTrial result = resultsIterator.getNextResult();
377     int trialCount = 0;
378     List<Triple<Integer, Double, int[]>> trialAssumptions = new ArrayList<Triple<Integer, Double, int[]>>();
379 
380     while (result != null) {
381 
382       // If user cancelled the task, stop
383       if (progress.isCancelled()) {
384         progress.taskCanceled();
385         break;
386       }
387 
388       if (!resultFilter.considerResult(result)) {
389         publishInfo(
390             progress,
391             "Results with ID " + result.getID()
392                 + " are dismissed by the result filter '"
393                 + Strings.dispClassName(resultFilter.getClass()));
394         result = resultsIterator.getNextResult();
395         continue;
396       }
397       publishInfo(progress, "Analyzing trial #" + (trialCount + 1));
398       try {
399         trialAssumptions.add(new Triple<Integer, Double, int[]>(trialCount,
400             result.getAssignmentProbability(), assumptionEncoder.encode(result
401                 .getAssignment())));
402       } catch (RuntimeException ex) {
403         GUI.printErrorMessage("Encoding trial failed.", ex);
404         result = resultsIterator.getNextResult();
405         continue;
406       }
407 
408       for (IAggregationSelector selector : selectors) {
409         selector.consider(trialCount, result);
410       }
411       trialCount++;
412 
413       result = resultsIterator.getNextResult();
414     }
415     return trialAssumptions;
416   }
417 
418   /**
419    * Publishes information to the logging mechanism and the progress observer.
420    * 
421    * @param progress
422    *          the progress observer
423    * @param msg
424    *          the information message
425    */
426   private void publishInfo(IProgressObserver progress, String msg) {
427     progress.incrementProgress(msg);
428     SimSystem.report(Level.INFO, msg);
429   }
430 
431   /**
432    * Initialize selectors.
433    * 
434    * @param projection
435    *          the projection
436    * @param selectors
437    *          the selectors
438    */
439   private void initializeSelectors(ProjectionModel projection,
440       IAggregationSelector[] selectors) {
441     for (IAggregationSelector selector : selectors) {
442       selector
443           .init(numOfTrials, numOfYears, projection.getNumberOfAgeClasses());
444     }
445   }
446 
447   /**
448    * Calculates quantiles per column.
449    * 
450    * @param inputMatrix
451    *          the input matrix
452    * 
453    * @return the double[][]
454    */
455   protected double[][] calcQuantiles(double[][] inputMatrix) {
456     if (inputMatrix.length == 0) {
457       return new double[0][0];
458     }
459     int maxRowIndex = inputMatrix.length - 1;
460     int cols = inputMatrix[0].length;
461     double[][] result = new double[QUANTILES.length][cols];
462     for (int col = 0; col < cols; col++) {
463       List<Double> currentValues = new ArrayList<Double>();
464       for (int i = 0; i < inputMatrix.length; i++) {
465         currentValues.add(inputMatrix[i][col]);
466       }
467       Collections.sort(currentValues);
468       for (int i = 0; i < QUANTILES.length; i++) {
469         result[i][col] = currentValues.get((int) (QUANTILES[i] * maxRowIndex));
470       }
471     }
472     return result;
473   }
474 
475   /**
476    * An alternative way to create the list of years for the density plot.
477    * 
478    * TODO: Should be replaced as soon as possible by
479    * {@link ResultExport#createEquidistantDensityYearList()}, this will also
480    * require a change to the densityPlot function in plotting.R.
481    * 
482    * 
483    * @param jumpOffYear
484    *          the jump off year
485    * @param firstOffset
486    *          the first offset
487    * @param regularOffset
488    *          the regular offset between years
489    * @return the string containing the density year list
490    * 
491    */
492   private String createDensityYearList(int jumpOffYear, int firstOffset,
493       int regularOffset) {
494     StringBuffer yearList = new StringBuffer();
495     for (int i = 0; i < DENSITY_PLOT_NUM_SUBPLOTS; i++) {
496       yearList.append(jumpOffYear + firstOffset + i * regularOffset
497           + (i == DENSITY_PLOT_NUM_SUBPLOTS - 1 ? "" : ","));
498     }
499     return yearList.toString();
500   }
501 
502   /**
503    * Creates the density year list. The density years are equidistant among
504    * jump-off year and the last year of the projection.
505    * 
506    * @return the string containing the density year list
507    */
508   protected String createEquidistantDensityYearList(int jumpOffYear) {
509     StringBuffer yearList = new StringBuffer();
510     for (int i = 0; i < DENSITY_PLOT_NUM_SUBPLOTS; i++) {
511       yearList.append((jumpOffYear + (int) Math.round(i * numOfYears
512           / (DENSITY_PLOT_NUM_SUBPLOTS - 1.0)))
513           + (i == DENSITY_PLOT_NUM_SUBPLOTS - 1 ? "" : ","));
514     }
515     return yearList.toString();
516   }
517 
518   /**
519    * Creates the full year list.
520    * 
521    * @param jumpOffYear
522    *          the jump off year
523    * @param projectionYears
524    *          the projection years
525    * @return the string
526    */
527   private String createFullYearList(int jumpOffYear, int projectionYears) {
528     StringBuffer yearList = new StringBuffer(Integer.toString(jumpOffYear));
529     yearList.append(',');
530     int lastYear = jumpOffYear;
531     for (int i = 0; i < projectionYears - 1; i++) {
532       lastYear++;
533       yearList.append(lastYear);
534       yearList.append(',');
535     }
536     yearList.append("" + (lastYear + 1));
537     return yearList.toString();
538   }
539 
540   /**
541    * Filter out all unnecessary data.
542    * 
543    * @param original
544    *          the original
545    * 
546    * @return the double[][]
547    */
548   protected double[][] filter(double[][] original) {
549     double[][] filteredResult = new double[original.length][yearOffsetToConsider.length + 1];
550     for (int i = 0; i < original.length; i++) {
551       filteredResult[i][0] = original[i][0];
552       int currentOffset = 0;
553       for (int j = 0; j < yearOffsetToConsider.length; j++) { // NOSONAR
554         currentOffset += yearOffsetToConsider[j];
555         filteredResult[i][j + 1] = original[i][currentOffset];
556       }
557     }
558     return filteredResult;
559   }
560 
561   /**
562    * Write result.
563    * 
564    * @param destinationDir
565    *          the destination dir
566    * @param result
567    *          the result
568    * @param fileName
569    *          the file name
570    * 
571    * @throws IOException
572    *           Signals that an I/O exception has occurred.
573    */
574   protected void writeResult(File destinationDir, double[][] result,
575       String fileName) throws IOException {
576     FileWriter fw = new FileWriter(destinationDir.getAbsolutePath()
577         + File.separatorChar + fileName);
578     fw.append(toCSV(result, ','));
579     fw.close();
580   }
581 
582   /**
583    * Export data.
584    * 
585    * @param dataDirectory
586    *          the data directory
587    * 
588    * @throws IOException
589    *           Signals that an I/O exception has occurred.
590    */
591   private void exportData(File dataDirectory) throws IOException {
592     SimSystem.report(Level.INFO, "Exporting CSV data...");
593     IProjectionResultsIterator resultsSet = DatabaseFactory
594         .getDatabaseSingleton().getResultIterator(projection);
595 
596     ParameterAssumptionEncoder assumptionEncoder = storeAssumptionEncoding(
597         projection, dataDirectory);
598 
599     int resultCount = 1;
600     for (ResultsOfTrial results : resultsSet) {
601       SimSystem.report(Level.INFO, "Export Trial #" + resultCount + " (ID: "
602           + results.getID() + ")");
603       if (!resultFilter.considerResult(results)) {
604         SimSystem.report(
605             Level.INFO,
606             "Results with ID " + results.getID()
607                 + " are dismissed by the result filter '"
608                 + Strings.dispClassName(resultFilter.getClass()));
609         continue;
610       }
611 
612       File trialDirectory = new File(dataDirectory.getAbsolutePath()
613           + File.separatorChar + "trial_" + resultCount);
614       if (!trialDirectory.mkdir()) {
615         throw new IllegalStateException("Could not create directory: "
616             + trialDirectory.getAbsolutePath());
617       }
618       exportBasicResults(trialDirectory, results.getNativesResults(), "natives");
619       writeGenerationResults(trialDirectory, results.getImmigrantResults(),
620           "immigrants");
621       writeGenerationResults(trialDirectory, results.getEmigrantResults(),
622           "emigrants");
623       writeAssumptionsUsed(trialDirectory, results, assumptionEncoder);
624       resultCount++;
625     }
626   }
627 
628   /**
629    * Writes a file that specifies which assumptions are used for the parameters
630    * in a given trial.
631    * 
632    * @param trialDirectory
633    *          the trial directory
634    * @param results
635    *          the results of the trial
636    * @param assumptionEncoder
637    *          the encoder for the assumptions that are used
638    * 
639    * @throws IOException
640    *           Signals that an I/O exception has occurred.
641    */
642   private void writeAssumptionsUsed(File trialDirectory,
643       ResultsOfTrial results, ParameterAssumptionEncoder assumptionEncoder)
644       throws IOException {
645 
646     FileWriter fw = new FileWriter(trialDirectory.getAbsolutePath()
647         + File.separatorChar + "assumptions.txt");
648     fw.append("Assumption probability: \t" + results.getAssignmentProbability()
649         + "\n");
650     fw.append("Numerical encoding: \t"
651         + Strings.dispArray(assumptionEncoder.encode(results.getAssignment()))
652         + "\n");
653     fw.append("\n\nAssumptions: \n"
654         + assumptionEncoder.verboseEncoding(results.getAssignment()) + "\n");
655     fw.close();
656   }
657 
658   /**
659    * Write results for a list of basic results that correspond to different
660    * generations of the same population.
661    * 
662    * @param trialDirectory
663    *          the trial directory
664    * @param results
665    *          the results to be written to file
666    * @param populationName
667    *          the name of the population
668    * 
669    * @throws IOException
670    *           Signals that an I/O exception has occurred.
671    */
672   private void writeGenerationResults(File trialDirectory,
673       List<BasicResults> results, String populationName) throws IOException {
674     for (int i = 0; i < results.size(); i++) {
675       exportBasicResults(trialDirectory, results.get(i), populationName
676           + "_gen_" + i);
677     }
678   }
679 
680   /**
681    * Export basic results.
682    * 
683    * @param trialDirectory
684    *          the trial directory
685    * @param basicResults
686    *          the basic results
687    * @param exportPrefix
688    *          the export prefix
689    * 
690    * @throws IOException
691    *           Signals that an I/O exception has occurred.
692    */
693   private void exportBasicResults(File trialDirectory,
694       BasicResults basicResults, String exportPrefix) throws IOException {
695     exportMatrix(trialDirectory, basicResults.getEndXm(), exportPrefix
696         + "_end_x_m.csv");
697     exportMatrix(trialDirectory, basicResults.getEndXf(), exportPrefix
698         + "_end_x_f.csv");
699     exportMatrix(trialDirectory, basicResults.getMeanXm(), exportPrefix
700         + "_mean_x_m.csv");
701     exportMatrix(trialDirectory, basicResults.getMeanXf(), exportPrefix
702         + "_mean_x_f.csv");
703   }
704 
705   /**
706    * Export matrix.
707    * 
708    * @param trialDirectory
709    *          the trial directory
710    * @param data
711    *          the data
712    * @param fileName
713    *          the file name
714    * 
715    * @throws IOException
716    *           Signals that an I/O exception has occurred.
717    */
718   private static void exportMatrix(File trialDirectory, Matrix2D data,
719       String fileName) throws IOException {
720     if (data == null) {
721       SimSystem.report(Level.INFO, "WARNING: Cannot write '" + fileName
722           + "' - data is null.");
723       return;
724     }
725     FileWriter fw = new FileWriter(trialDirectory.getAbsolutePath()
726         + File.separatorChar + fileName);
727     fw.append(toCSV(data.toArray(), ','));
728     fw.close();
729   }
730 
731   /**
732    * Initialize sub directory.
733    * 
734    * @param subDirName
735    *          the name of the sub-directory
736    * 
737    * @return the file
738    */
739   private File initializeSubDirectory(String subDirName) {
740     File subDirectory = new File(targetDir.getAbsolutePath()
741         + File.separatorChar + subDirName);
742 
743     if (subDirectory.exists()
744         && (!deleteAllDirFiles(subDirectory) || !subDirectory.delete())) {
745       throw new IllegalArgumentException("Could not delete directory: "
746           + subDirectory.getAbsolutePath());
747     }
748 
749     if (!subDirectory.mkdir()) {
750       throw new IllegalArgumentException("Could not create directory: "
751           + subDirectory.getAbsolutePath());
752     }
753     return subDirectory;
754   }
755 
756   /**
757    * Delete all files in a directory.
758    * 
759    * @param dir
760    *          the directory
761    * 
762    * @return true, if successful
763    */
764   private boolean deleteAllDirFiles(File dir) {
765     for (File subFile : dir.listFiles()) {
766       if (!subFile.delete()) {
767         return false;
768       }
769     }
770     return true;
771   }
772 
773   /**
774    * Copies plotting library to target directory.
775    * 
776    * @throws IOException
777    *           if copying fails
778    */
779   private void copyPlottingLib() throws IOException {
780 
781     // TODO: User JAMESII result reporting instead
782 
783     String sourcePath = '/' + TEMPLATE_DIRECTORY + '/' + R_LIBRARY;
784     String targetPath = targetDir.getAbsolutePath() + File.separatorChar
785         + R_LIBRARY;
786 
787     BufferedInputStream source = null;
788     BufferedOutputStream target = null;
789     final String errorMessage = "Could not copy library file from '"
790         + sourcePath + "' to '" + targetPath
791         + "' (required to execute R code).";
792     try {
793       source = new BufferedInputStream(getClass().getResourceAsStream(
794           sourcePath));
795       target = new BufferedOutputStream(new FileOutputStream(targetPath));
796       byte[] buffer = new byte[COPY_BUFFER_SIZE];
797       int len = source.read(buffer);
798       while (len != -1) {
799         target.write(buffer, 0, len);
800         len = source.read(buffer);
801       }
802     } catch (IOException e) {
803       GUI.printErrorMessage(errorMessage, e);
804     } finally {
805       try {
806         if (target != null) {
807           target.close();
808         }
809       } catch (IOException e) {
810         GUI.printErrorMessage(errorMessage, e);
811       }
812       try {
813         if (source != null) {
814           source.close();
815         }
816       } catch (IOException e) {
817         GUI.printErrorMessage(errorMessage, e);
818       }
819     }
820   }
821 
822   /**
823    * Export to CSV.
824    * 
825    * @param matrix
826    *          the matrix
827    * @param delim
828    *          the delimiter
829    * 
830    * @return the string builder
831    */
832   public static StringBuilder toCSV(double[][] matrix, char delim) {
833     if (matrix == null) {
834       return new StringBuilder("null");
835     }
836     StringBuilder matrixString = new StringBuilder();
837     for (int i = 0; i < matrix.length; i++) {
838       for (int j = 0; j < matrix[i].length; j++) {
839         matrixString.append(Double.toString(matrix[i][j]));
840         if (j < matrix[i].length - 1) {
841           matrixString.append(delim);
842         }
843       }
844       matrixString.append('\n');
845     }
846     return matrixString;
847   }
848 
849   /**
850    * Creates the Sweave file, inserts all data contianed in map.
851    * 
852    * @param dataToBeInserted
853    *          map containing the pointers to the aggregated data
854    * @throws IOException
855    *           if file creation fails
856    * @throws TemplateException
857    *           if template processing fails
858    */
859   private void createSweaveFile(Map<String, Object> dataToBeInserted)
860       throws IOException, TemplateException {
861 
862     SimSystem.report(Level.INFO, "Creating report template.");
863 
864     Configuration cfg = new Configuration();
865     cfg.setClassForTemplateLoading(ResultExport.class, "/" + TEMPLATE_DIRECTORY);
866     cfg.setObjectWrapper(new DefaultObjectWrapper());
867 
868     dataToBeInserted.put("projection", projection);
869     dataToBeInserted.put("ages", projection.getMaximumAge() + 1);
870     dataToBeInserted.put("years", projection.getYears() + 1);
871     dataToBeInserted.put("trials", numOfTrials);
872     dataToBeInserted.put("date", (new SimpleDateFormat(
873         "dd. MM. yyyy (HH:mm:ss)")).format(Calendar.getInstance().getTime()));
874     dataToBeInserted.put("numsettypes", projection.getAllSetTypes().size());
875 
876     // Add index list of generations
877     List<Integer> generations = new ArrayList<Integer>();
878     for (int i = 0; i < projection.getGenerations(); i++) {
879       generations.add(i);
880     }
881     dataToBeInserted.put("generations", generations);
882 
883     // Add index list of descendant generations
884     List<Integer> descendantGenerations = new ArrayList<Integer>();
885     for (int i = 1; i < projection.getGenerations(); i++) {
886       descendantGenerations.add(i);
887     }
888     dataToBeInserted.put("desc_generations", descendantGenerations);
889 
890     Template basicTemplate = cfg.getTemplate("template.Rtfm");
891     Writer out = new FileWriter(targetDir.getAbsolutePath()
892         + File.separatorChar + "report.Rtex");
893     basicTemplate.process(dataToBeInserted, out);
894     out.close();
895   }
896 
897   /**
898    * Gets the result filter.
899    * 
900    * @return the result filter
901    */
902   public IResultFilter getResultFilter() {
903     return resultFilter;
904   }
905 
906 }