package beast.app.tools;
import java.awt.BorderLayout;
import java.awt.Font;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import beast.app.BEASTVersion2;
import beast.app.util.Utils;
import beast.app.util.WholeNumberField;
import beast.core.util.Log;
import beast.evolution.tree.coalescent.CompoundPopulationFunction;
import beast.evolution.tree.coalescent.CompoundPopulationFunction.Type;
import beast.math.statistic.DiscreteStatistics;
import beast.util.HeapSort;
import jam.console.ConsoleApplication;
import jam.panels.OptionsPanel;
public class EBSPAnalyser {
String m_sFileOut;
PrintStream m_out = System.out;
CompoundPopulationFunction.Type m_type = Type.LINEAR;
String m_sInputFile;
int m_nBurninPercentage = 10;
private void run() throws IOException {
parse(m_sInputFile, m_nBurninPercentage, m_type, m_out);
}
void parse(String fileName, int burnInPercentage, CompoundPopulationFunction.Type type, PrintStream out) throws IOException {
logln("Processing " + fileName);
BufferedReader fin = new BufferedReader(new FileReader(fileName));
String str;
int data = 0;
// first, sweep through the log file to determine size of the log
while (fin.ready()) {
str = fin.readLine();
// terrible hackish code, must improve later
if( str.charAt(0) == '#' ) {
int i = str.indexOf("spec=");
if( i > 0 ) {
if( str.indexOf("type=\"stepwise\"") > 0 ) {
m_type = Type.STEPWISE;
} else if( str.indexOf("type=\"linear\"") > 0 ) {
m_type = Type.LINEAR;
}
}
}
if (str.indexOf('#') < 0 && str.matches(".*[0-9a-zA-Z].*")) {
data++;
}
}
final int burnIn = data * burnInPercentage / 100;
logln(" skipping " + burnIn + " line\n\n");
data = -burnIn - 1;
fin.close();
fin = new BufferedReader(new FileReader(fileName));
// process log
final List<List<Double>> times = new ArrayList<>();
final List<List<Double>> popSizes = new ArrayList<>();
double[] alltimes = null;
while (fin.ready()) {
str = fin.readLine();
if (str.indexOf('#') < 0 && str.matches(".*[0-9a-zA-Z].*")) {
if (++data > 0) {
final String[] strs = str.split("\t");
final List<Double> times2 = new ArrayList<>();
final List<Double> popSizes2 = new ArrayList<>();
if (alltimes == null) {
alltimes = new double[strs.length - 1];
}
for (int i = 1; i < strs.length; i++) {
final String[] strs2 = strs[i].split(":");
final Double time = Double.parseDouble(strs2[0]);
alltimes[i - 1] += time;
if (strs2.length > 1) {
times2.add(time);
popSizes2.add(Double.parseDouble(strs2[1]));
}
}
times.add(times2);
popSizes.add(popSizes2);
}
}
}
if (alltimes == null) {
//burn-in too large?
return;
}
// take average of coalescent times
for (int i = 0; i < alltimes.length; i++) {
alltimes[i] /= times.size();
}
// generate output
out.println("time\tmean\tmedian\t95HPD lower\t95HPD upper");
final double[] popSizeAtTimeT = new double[times.size()];
int[] indices = new int[times.size()];
for (final double time : alltimes) {
for (int j = 0; j < popSizeAtTimeT.length; j++) {
popSizeAtTimeT[j] = calcPopSize(type, times.get(j), popSizes.get(j), time);
}
HeapSort.sort(popSizeAtTimeT, indices);
out.print(time + "\t");
out.print(DiscreteStatistics.mean(popSizeAtTimeT) + "\t");
out.print(DiscreteStatistics.median(popSizeAtTimeT) + "\t");
double[] hpdInterval = DiscreteStatistics.HPDInterval(0.95, popSizeAtTimeT, indices);
out.println(hpdInterval[0] + "\t" + hpdInterval[1]);
}
}
private double calcPopSize(CompoundPopulationFunction.Type type, List<Double> xs, List<Double> ys, double d) {
// TODO completely untested
// assume linear
//assert typeName.equals("Linear");
final int n = xs.size();
final double xn = xs.get(n - 1);
if (d >= xn) {
return ys.get(n - 1);
}
assert d >= xs.get(0);
int i = 1;
while (d >= xs.get(i)) {
++i;
}
// d < xs.get(i)
double x0 = xs.get(i-1);
double x1 = xs.get(i);
double y0 = ys.get(i-1);
double y1 = ys.get(i);
assert x0 <= d && d <= x1 : "" + x0 + "," + x1 + "," + d;
switch (type) {
case LINEAR:
final double p = (d * (y1 - y0) + (y0 * x1 - y1 * x0)) / (x1 - x0);
assert p > 0;
return p;
case STEPWISE:
assert y1 > 0;
return y1;
}
return 0;
}
private void parseArgs(String[] args) {
int i = 0;
try {
while (i < args.length) {
int old = i;
if (i < args.length) {
if (args[i].equals("")) {
i += 1;
} else if (args[i].equals("-help") || args[i].equals("-h") || args[i].equals("--help")) {
System.out.println(getUsage());
System.exit(0);
} else if (args[i].equals("-i")) {
m_sInputFile = args[i + 1];
i += 2;
} else if (args[i].equals("-o")) {
m_sFileOut = args[i + 1];
m_out = new PrintStream(m_sFileOut);
i += 2;
} else if (args[i].equals("-type")) {
if (args[i + 1].equals("linear")) {
m_type = Type.LINEAR;
} else if (args[i + 1].equals("stepwise")) {
m_type = Type.STEPWISE;
} else {
throw new IllegalArgumentException("Expected linear or stepwise, not " + args[i + 1]);
}
i += 2;
} else if (args[i].equals("-burnin")) {
m_nBurninPercentage = Integer.parseInt(args[i + 1]);
i += 2;
}
if (i == old) {
throw new IllegalArgumentException("Unrecognised argument (argument " + i + ": " + args[i] + ")");
}
}
}
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException("Error parsing command line arguments: " + Arrays.toString(args) + "\nArguments ignored\n\n" + getUsage());
}
if (m_sFileOut == null) {
Log.warning.println("No output file specified");
}
}
static String getUsage() {
return "EBSPAnalyse -i <inputfile> [options]\n" +
"analyses trace file generated by EBSP analysis\n" +
"Options are:\n" +
"-i <inputfile> name of input file (required)\n" +
"-burnin <percentage> percent of log to consider burn in, default 10\n" +
"-type [linear|step] type of population function\n" +
"-o <outputfile> name of output file, default to output on stdout\n" +
"";
}
protected void log(String s) {
Log.warning.print(s);
}
protected void logln(String s) {
Log.warning.println(s);
}
private void printTitle(String aboutString) {
aboutString = "LogCombiner" + aboutString.replaceAll("</p>", "\n\n");
aboutString = aboutString.replaceAll("<br>", "\n");
aboutString = aboutString.replaceAll("<[^>]*>", " ");
String[] strs = aboutString.split("\n");
for (String str : strs) {
int n = 80 - str.length();
int n1 = n / 2;
for (int i = 0; i < n1; i++) {
log(" ");
}
logln(str);
}
}
public class EBSPAnalyserDialog {
private final JFrame frame;
private final OptionsPanel optionPanel;
private final JTextField inputFileNameText = new JTextField("not selected", 16);
private final JComboBox<String> typeCombo = new JComboBox<>(new String[]{"linear", "stepwise"});
final WholeNumberField burninText = new WholeNumberField(0, Long.MAX_VALUE);
private final JTextField outputFileNameText = new JTextField("not selected", 16);
private File outputFile = null;
private File inputFile = null;
public EBSPAnalyserDialog(final JFrame frame, String titleString, Icon icon) {
this.frame = frame;
optionPanel = new OptionsPanel(12, 12);
final JLabel titleText = new JLabel(titleString);
titleText.setIcon(icon);
optionPanel.addSpanningComponent(titleText);
Font font = UIManager.getFont("Label.font");
titleText.setFont(new Font("sans-serif", font.getStyle(), font.getSize()));
JPanel panel = new JPanel(new BorderLayout());
panel.setOpaque(false);
JButton button = new JButton("Choose Input File...");
button.addActionListener(ae -> {
File file = Utils.getLoadFile("Select input file...");
if (file == null) {
// the dialog was cancelled...
return;
}
inputFile = file;
inputFileNameText.setText(inputFile.getName());
});
inputFileNameText.setEditable(false);
JButton button2 = new JButton("Choose Output File...");
button2.addActionListener(ae -> {
File file = Utils.getSaveFile("Select output file...");
if (file == null) {
// the dialog was cancelled...
return;
}
outputFile = file;
outputFileNameText.setText(outputFile.getName());
});
outputFileNameText.setEditable(false);
JPanel panel1 = new JPanel(new BorderLayout(0, 0));
panel1.add(inputFileNameText, BorderLayout.CENTER);
panel1.add(button, BorderLayout.EAST);
optionPanel.addComponentWithLabel("Input File: ", panel1);
optionPanel.addComponentWithLabel("File type: ", typeCombo);
burninText.setColumns(12);
burninText.setValue(10);
optionPanel.addComponentWithLabel("Burn in percentage: ", burninText);
optionPanel.addSpanningComponent(panel);
JPanel panel3 = new JPanel(new BorderLayout(0, 0));
panel3.add(outputFileNameText, BorderLayout.CENTER);
panel3.add(button2, BorderLayout.EAST);
optionPanel.addComponentWithLabel("Output File: ", panel3);
}
public boolean showDialog(String title) {
JOptionPane optionPane = new JOptionPane(optionPanel,
JOptionPane.PLAIN_MESSAGE,
JOptionPane.OK_CANCEL_OPTION,
null,
new String[]{"Run", "Quit"},
null);
optionPane.setBorder(new EmptyBorder(12, 12, 12, 12));
final JDialog dialog = optionPane.createDialog(frame, title);
//dialog.setResizable(true);
dialog.pack();
dialog.setVisible(true);
return optionPane.getValue().equals("Run");
}
public String getOutputFileName() {
if (outputFile == null) return null;
return outputFile.getPath();
}
public String[] getArgs() {
java.util.List<String> args = new ArrayList<>();
if (inputFile != null) {
args.add("-i");
args.add(inputFile.getPath());
}
args.add("-burnin");
args.add(burninText.getText());
args.add("-type");
args.add(typeCombo.getSelectedItem().toString());
if (outputFile != null) {
args.add("-o");
args.add(outputFile.getPath());
}
return args.toArray(new String[0]);
}
}
/**
* @param args
*/
public static void main(String[] args) {
BEASTVersion2 version = new BEASTVersion2();
final String versionString = version.getVersionString();
String nameString = "EBSP Analyser " + versionString;
String aboutString = "<html><center><p>" + versionString + ", " + version.getDateString() + "</p>" +
"<p>by<br>" +
"<p>Joseph Heled and Remco Bouckaert</p>" +
"<p>Department of Computer Science, University of Auckland<br>" +
"<a href=\"mailto:jheled@gmail.com\">jheled@gmail.com</a></p>" +
"<a href=\"mailto:remco@cs.auckland.ac.nz\">remco@cs.auckland.ac.nz</a></p>" +
"<p>Part of the BEAST 2 package:<br>" +
"<a href=\"http://beast2.cs.auckland.ac.nz/\">http://beast2.cs.auckland.ac.nz/</a></p>" +
"</center></html>";
try {
EBSPAnalyser analyser = new EBSPAnalyser();
if (args.length == 0) {
Utils.loadUIManager();
System.setProperty("com.apple.macos.useScreenMenuBar", "true");
System.setProperty("apple.laf.useScreenMenuBar", "true");
System.setProperty("apple.awt.showGrowBox", "true");
// TODO: set up ICON
java.net.URL url = EBSPAnalyser.class.getResource("images/EBSPAnalyser.png");
javax.swing.Icon icon = null;
if (url != null) {
icon = new javax.swing.ImageIcon(url);
}
//ConsoleApplication consoleApp =
new ConsoleApplication(nameString, aboutString, icon, true);
analyser.printTitle(aboutString);
String titleString = "<html><center><p>EBSPAnalyser<br>" +
"Version " + version.getVersionString() + ", " + version.getDateString() + "</p></center></html>";
EBSPAnalyserDialog dialog = analyser.new EBSPAnalyserDialog(new JFrame(), titleString, icon);
if (!dialog.showDialog(nameString)) {
return;
}
String[] args2 = dialog.getArgs();
try {
analyser.parseArgs(args2);
analyser.run();
} catch (Exception ex) {
Log.err.println("Exception: " + ex.getMessage());
ex.printStackTrace();
}
System.out.println("Finished - Quit program to exit.");
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
analyser.printTitle(aboutString);
analyser.parseArgs(args);
analyser.run();
}
} catch (Exception e) {
System.out.println(getUsage());
e.printStackTrace();
}
}
}