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