package test.beast.beast2vs1.trace; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; /** * A class that stores a set of traces from a single chain * * @author Andrew Rambaut * @author Alexei Drummond * @author Walter Xie */ public class LogFileTraces { protected final File file; protected final String name; private final List<String> tracesNameList = new ArrayList<String>(); protected List<List<Double>> valuesList = new ArrayList<List<Double>>(); private long burnIn = -1; private long firstState = -1; private long lastState = -1; private long stepSize = -1; public LogFileTraces(String name, File file) { this.name = name; this.file = file; } /** * @return the name of this traceset */ public String getName() { return name; } public File getFile() { return file; } /** * @return the last state in the chain */ public long getMaxState() { return lastState; } public boolean isIncomplete() { return false; } /** * @return the number of states excluding the burnin */ public long getStateCount() { // This is done as two integer divisions to ensure the same rounding for // the burnin... return ((lastState - firstState) / stepSize) - (getBurnIn() / stepSize) + 1; } /** * @return the number of states in the burnin */ public long getBurninStateCount() { return (getBurnIn() / stepSize); } /** * @return the size of the step between states */ public long getStepSize() { return stepSize; } public long getBurnIn() { return burnIn; } /** * @return the number of traces in this traceset */ public int getTraceCount() { return tracesNameList.size(); } public void setBurnIn(long burnIn) { this.burnIn = burnIn; } public void loadTraces() throws TraceException, IOException { FileReader reader = new FileReader(file); loadTraces(reader); reader.close(); } public void loadTraces(Reader r) throws TraceException, IOException { TrimLineReader reader = new TrimLineReader(r); // lines starting with [ are ignored, assuming comments in MrBayes file // lines starting with # are ignored, assuming comments in Migrate or BEAST file StringTokenizer tokens = reader.readTokensIgnoringEmptyLinesAndComments(new String[]{"[", "#"}); // read label tokens String[] labels = new String[tokens.countTokens()]; for (int i = 0; i < labels.length; i++) { labels[i] = tokens.nextToken(); tracesNameList.add(labels[i]); valuesList.add(new ArrayList<Double>()); } int traceCount = getTraceCount(); boolean firstState = true; tokens = reader.tokenizeLine(); while (tokens != null && tokens.hasMoreTokens()) { String stateString = tokens.nextToken(); long state = 0; try { try { // Changed this to parseDouble because LAMARC uses scientific notation for the state number state = (long) Double.parseDouble(stateString); } catch (NumberFormatException nfe) { throw new TraceException("Unable to parse state number in column 1 (Line " + reader.getLineNumber() + ")"); } if (firstState) { // MrBayes puts 1 as the first state, BEAST puts 0 // In order to get the same gap between subsequent samples, // we force this to 0. if (state == 1) state = 0; firstState = false; } if (!addState(state)) { throw new TraceException("State " + state + " is not consistent with previous spacing (Line " + reader.getLineNumber() + ")"); } } catch (NumberFormatException nfe) { reader.close(); throw new TraceException("State " + state + ":Expected real value in column " + reader.getLineNumber()); } for (int i = 0; i < traceCount; i++) { if (tokens.hasMoreTokens()) { String value = tokens.nextToken(); try { valuesList.get(i).add(Double.parseDouble(value)); } catch (NumberFormatException nfe) { reader.close(); throw new TraceException("State " + state + ": Expected correct number type (Double) in column " + (i + 1) + " (Line " + reader.getLineNumber() + ")"); } } else { reader.close(); throw new TraceException("State " + state + ": missing values at line " + reader.getLineNumber()); } } tokens = reader.tokenizeLine(); } burnIn = (int) (0.1 * lastState); reader.close(); } /** * Add a state number for these traces. This should be * called before adding values for each trace. The spacing * between stateNumbers should remain constant. * * @param stateNumber the state * @return false if the state number is inconsistent */ private boolean addState(long stateNumber) { if (firstState < 0) { firstState = stateNumber; } else if (stepSize < 0) { stepSize = stateNumber - firstState; } else { long step = stateNumber - lastState; if (step != stepSize) { return false; } } lastState = stateNumber; return true; } public TraceStatistics analyseTrace(int index) { int start = (int) (getBurnIn() / getStepSize()); List<Double> values = valuesList.get(index).subList(start, valuesList.get(index).size()); double[] doubleValues = new double[values.size()]; for (int i = 0; i < values.size(); i++) { doubleValues[i] = values.get(i); } return new TraceStatistics(doubleValues, getStepSize()); } public String getTraceName(int i) { return tracesNameList.get(i); } public int getTraceIndex(String traceName) { return tracesNameList.indexOf(traceName); } }