/* * LogFileTraces.java * * Copyright (C) 2002-2006 Alexei Drummond and Andrew Rambaut * * This file is part of BEAST. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * BEAST is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package dr.inference.trace; import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.TreeMap; /** * A class that stores a set of traces from a single chain * * @author Andrew Rambaut * @author Alexei Drummond * @version $Id: LogFileTraces.java,v 1.4 2006/11/30 17:39:29 rambaut Exp $ */ public class LogFileTraces extends AbstractTraceList { 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 int getMaxState() { return lastState; } public boolean isIncomplete() { return false; } /** * @return the number of states excluding the burnin */ public int 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 int getBurninStateCount() { return (getBurnIn() / stepSize); } /** * @return the size of the step between states */ public int getStepSize() { return stepSize; } public int getBurnIn() { return burnIn; } /** * @return the number of traces in this traceset */ public int getTraceCount() { return traces.size(); } /** * @return the index of the trace with the given name */ public int getTraceIndex(String name) { for (int i = 0; i < traces.size(); i++) { Trace trace = getTrace(i); if (name.equals(trace.getName())) { return i; } } return -1; } /** * @return the name of the trace with the given index */ public String getTraceName(int index) { return getTrace(index).getName(); } /** * @param index requested trace index * @return the trace for a given index */ public Trace getTrace(int index) { return traces.get(index); } public void setBurnIn(int burnIn) { this.burnIn = burnIn; for (Trace trace : traces) { trace.setTraceStatistics(null); } } public double getStateValue(int trace, int index) { return (Double) getTrace(trace).getValue(index + (burnIn / stepSize)); } /** * Read several consecutive values of one state into a destination array * * @param nState State index number * @param destination array to store result * @param offset first trace index */ public void getStateValues(int nState, double[] destination, int offset) { final int index1 = nState + (burnIn / stepSize); for (int k = 0; k < destination.length; ++k) { destination[k] = (Double) getTrace(k + offset).getValue(index1); } } public List getValues(int index, int fromIndex, int toIndex) { List newList = null; try { newList = getTrace(index).getValues(fromIndex, toIndex, selected); } catch (Exception e) { System.err.println("getValues error: trace index = " + index); } return newList; } public List getValues(int index) { return this.getValues(index, getBurninStateCount(), getTrace(index).getValuesSize()); } public List getBurninValues(int index) { return this.getValues(index, 0, getBurninStateCount()); } public void loadTraces() throws TraceException, IOException { FileReader reader = new FileReader(file); loadTraces(reader); reader.close(); } /** * Walter: Please comment what the extra arguments mean * * @param r * @throws TraceException * @throws java.io.IOException */ public void loadTraces(Reader r) throws TraceException, java.io.IOException { TrimLineReader reader = new LogFileTraces.TrimLineReader(r); // Read through to first token StringTokenizer tokens = reader.tokenizeLine(); if (tokens == null) { throw new TraceException("Trace file is empty."); } // read over empty lines while (!tokens.hasMoreTokens()) { tokens = reader.tokenizeLine(); } // skip the first column which should be the state number String token = tokens.nextToken(); // lines starting with [ are ignored, assuming comments in MrBayes file // lines starting with # are ignored, assuming comments in Migrate or BEAST file while (token.startsWith("[") || token.startsWith("#")) { readTraceType(token, tokens); // using # to define type tokens = reader.tokenizeLine(); // read over empty lines while (!tokens.hasMoreTokens()) { tokens = reader.tokenizeLine(); } // read state token and ignore token = tokens.nextToken(); } // read label tokens String[] labels = new String[tokens.countTokens()]; for (int i = 0; i < labels.length; i++) { labels[i] = tokens.nextToken(); addTraceAndType(labels[i]); } int traceCount = getTraceCount(); boolean firstState = true; tokens = reader.tokenizeLine(); while (tokens != null && tokens.hasMoreTokens()) { String stateString = tokens.nextToken(); int state = 0; try { try { // Changed this to parseDouble because LAMARC uses scientific notation for the state number state = (int) 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) { 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(); if (state == 0) assignTraceTypeAccordingValue(value); try { // values[i] = Double.parseDouble(tokens.nextToken()); addParsedValue(i, value); } catch (NumberFormatException nfe) { throw new TraceException("State " + state + ": Expected correct number type (Double, Integer or String) in column " + (i + 1) + " (Line " + reader.getLineNumber() + ")"); } } else { throw new TraceException("State " + state + ": missing values at line " + reader.getLineNumber()); } } tokens = reader.tokenizeLine(); } burnIn = (int) (0.1 * lastState); } /** * add a value for the n'th trace * * @param nTrace trace index * @param value next value */ private void addParsedValue(int nTrace, String value) { String name = getTraceName(nTrace); // System.out.println(thisTrace.getTraceType() + " " + value); if (tracesType.get(name) == TraceFactory.TraceType.DOUBLE || tracesType.get(name) == TraceFactory.TraceType.INTEGER) { Double v = Double.parseDouble(value); getTrace(nTrace).add(v); } else if (tracesType.get(name) == TraceFactory.TraceType.STRING) { getTrace(nTrace).add(value); } else { throw new RuntimeException("Trace type is not recognized: " + tracesType.get(name)); } } private void assignTraceTypeAccordingValue(String value) { //todo } private void readTraceType(String firstToken, StringTokenizer tokens) { if (tokens.hasMoreTokens()) { String token; //= tokens.nextToken(); if (firstToken.contains(TraceFactory.TraceType.INTEGER.toString()) || firstToken.contains(TraceFactory.TraceType.INTEGER.toString().toUpperCase())) { while (tokens.hasMoreTokens()) { token = tokens.nextToken(); tracesType.put(token, TraceFactory.TraceType.INTEGER); } } else if (firstToken.contains(TraceFactory.TraceType.STRING.toString()) || firstToken.contains(TraceFactory.TraceType.STRING.toString().toUpperCase())) { while (tokens.hasMoreTokens()) { token = tokens.nextToken(); tracesType.put(token, TraceFactory.TraceType.STRING); } } } } // public Trace<?> assignTraceType(String name, int numberOfLines) { // Trace<?> trace = null; // if (tracesType != null) { // if (tracesType.get(name) == TraceFactory.TraceType.INTEGER) { // trace = new DiscreteTrace(name, numberOfLines); //// trace.setTraceType(TraceType.INTEGER); // } else if (tracesType.get(name) == TraceFactory.TraceType.STRING) { // trace = new CategoryTrace(name, numberOfLines); //// trace.setTraceType(TraceType.STRING); // } // } else { // trace = new ContinuousTrace(name, numberOfLines); // default DOUBLE // } // return trace; // } //************************************************************************ // private methods //************************************************************************ // These methods are used by the load function, above /** * Add a trace for a statistic of the given name * * @param name trace name */ private void addTraceAndType(String name) { if (tracesType == null || tracesType.get(name) == null || tracesType.get(name) == TraceFactory.TraceType.DOUBLE) { traces.add(createTrace(name, TraceFactory.TraceType.DOUBLE)); tracesType.put(name, TraceFactory.TraceType.DOUBLE); } else { traces.add(createTrace(name, tracesType.get(name))); } } private Trace createTrace(String name, TraceFactory.TraceType traceType) { // System.out.println("create trace (" + name + ") with type " + traceType); switch (traceType) { case DOUBLE: return new Trace<Double>(name, TraceFactory.TraceType.DOUBLE); case INTEGER: return new Trace<Double>(name, TraceFactory.TraceType.INTEGER); // use Double for legacy issue case STRING: return new Trace<String>(name, TraceFactory.TraceType.STRING); default: throw new IllegalArgumentException("The trace type " + traceType + " is not recognized."); } } public void changeTraceType(int id, TraceFactory.TraceType newType) throws TraceException { if (id >= getTraceCount() || id < 0) throw new TraceException("trace id is invaild " + id); Trace trace = traces.get(id); if (trace.getTraceType() != newType) { Trace newTrace = null; try { if (trace.getTraceType() == TraceFactory.TraceType.STRING) { if (newType == TraceFactory.TraceType.DOUBLE) { newTrace = createTrace(trace.getName(), TraceFactory.TraceType.DOUBLE); } else { newTrace = createTrace(trace.getName(), TraceFactory.TraceType.INTEGER); } for (int i = 0; i < trace.getValuesSize(); i++) { // String => Double newTrace.add(Double.parseDouble(trace.getValue(i).toString())); } } else if (newType == TraceFactory.TraceType.STRING) { newTrace = createTrace(trace.getName(), TraceFactory.TraceType.STRING); for (int i = 0; i < trace.getValuesSize(); i++) { // Double => String newTrace.add(trace.getValue(i).toString()); } } else { newTrace = createTrace(trace.getName(), newType); // not need to copy values, becaue they are both Double } } catch (Exception e) { throw new TraceException("Type change is failed, when parsing " + trace.getTraceType() + " to " + newType + " in trace " + trace.getName()); } if (trace.getTraceType() == TraceFactory.TraceType.STRING || newType == TraceFactory.TraceType.STRING) { if (newTrace.getValuesSize() != trace.getValuesSize()) throw new TraceException("Type change is failed, because values size is different after copy !"); traces.set(id, newTrace); } else { trace.setTraceType(newType); } } } /** * 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(int stateNumber) { if (firstState < 0) { firstState = stateNumber; } else if (stepSize < 0) { stepSize = stateNumber - firstState; } else { int step = stateNumber - lastState; if (step != stepSize) { return false; } } lastState = stateNumber; return true; } protected final File file; protected final String name; private final List<Trace> traces = new ArrayList<Trace>(); public void addTrace(String newTName, int i) { TraceCustomized tc = new TraceCustomized(newTName); tc.addValues(traces.get(i)); // only Double traces.add(tc); tracesType.put(newTName, TraceFactory.TraceType.DOUBLE); } // tracesType only save INTEGER and STRING, and only use during loading files private TreeMap<String, TraceFactory.TraceType> tracesType = new TreeMap<String, TraceFactory.TraceType>(); private int burnIn = -1; private int firstState = -1; private int lastState = -1; private int stepSize = -1; public static class TrimLineReader extends BufferedReader { public TrimLineReader(Reader reader) { super(reader); } public String readLine() throws java.io.IOException { lineNumber += 1; String line = super.readLine(); if (line != null) return line.trim(); return null; } public StringTokenizer tokenizeLine() throws java.io.IOException { String line = readLine(); if (line == null) return null; return new StringTokenizer(line, "\t"); } public int getLineNumber() { return lineNumber; } private int lineNumber = 0; } // public class D extends LogFileTraces implements TraceList.D { // // public D(String name, File file) { // super(name, file); // } // // public Double[] getValues(int index, int length) { // return this.getValues(index, length, 0); // } // // public Double[] getValues(int index, int length, int offset) { // Double[] destination = null; // try { // destination = ((Trace.D) getTrace(index)).getValues(length, getBurninStateCount(), offset, selected); // } catch (Exception e) { // System.err.println("getValues error: trace index = " + index); // } // return destination; // } // // public Double[] getBurninValues(int index, int length) { // Double[] destination = null; // try { // destination = (Double[]) getTrace(index).getValues(length, 0, 0, getBurninStateCount(), selected); // } catch (Exception e) { // System.err.println("getValues error: trace index = " + index); // } // return destination; // } // } }