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