/*
* File BeastMCMC.java
*
* Copyright (C) 2010 Remco Bouckaert remco@cs.auckland.ac.nz
*
* This file is part of BEAST2.
* 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 beast.app;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.border.EtchedBorder;
import javax.swing.filechooser.FileFilter;
import javax.xml.parsers.ParserConfigurationException;
import org.json.JSONException;
import org.xml.sax.SAXException;
import beagle.BeagleFlag;
import beast.app.beastapp.BeastDialog;
import beast.app.beastapp.BeastMain;
import beast.app.beauti.Beauti;
import beast.app.draw.ExtensionFileFilter;
import beast.app.util.Version;
import beast.core.Logger;
import beast.core.Runnable;
import beast.core.util.Log;
import beast.util.AddOnManager;
import beast.util.JSONParser;
import beast.util.JSONParserException;
import beast.util.Randomizer;
import beast.util.XMLParser;
import beast.util.XMLParserException;
import jam.util.IconUtils;
/**
* Main application for performing MCMC runs.
* See getUsage() for command line options.
*/
public class BeastMCMC {
final public static String VERSION = "2.0 Release candidate";
final public static String DEVELOPERS = "Beast 2 development team";
final public static String COPYRIGHT = "Beast 2 development team 2011";
/**
* number of threads used to run the likelihood beast.core *
*/
static public int m_nThreads = 1;
/**
* thread pool *
*/
public static ExecutorService g_exec = Executors.newFixedThreadPool(m_nThreads);
/**
* random number seed used to initialise Randomizer *
*/
long m_nSeed = 127;
/**
* MCMC object to execute *
*/
Runnable m_runnable;
/**
* parse command line arguments, and load file if specified
* @throws IOException
* @throws JSONException
* @throws JSONParserException
*/
public void parseArgs(String[] args) throws IOException, XMLParserException, JSONException {
int i = 0;
boolean resume = false;
boolean useStrictVersions = false;
File beastFile = null;
try {
while (i < args.length) {
int old = i;
if (i < args.length) {
if (args[i].equals("")) {
i += 1;
} else if (args[i].equals("-batch")) {
Logger.FILE_MODE = Logger.LogFileMode.only_new_or_exit;
i += 1;
} else if (args[i].equals("-resume")) {
resume = true;
Logger.FILE_MODE = Logger.LogFileMode.resume;
System.setProperty("beast.resume", "true");
System.setProperty("beast.debug", "false");
i += 1;
} else if (args[i].equals("-overwrite")) {
Logger.FILE_MODE = Logger.LogFileMode.overwrite;
i += 1;
} else if (args[i].equals("-seed")) {
if (args[i + 1].equals("random")) {
m_nSeed = Randomizer.getSeed();
} else {
m_nSeed = Long.parseLong(args[i + 1]);
}
i += 2;
} else if (args[i].equals("-threads")) {
m_nThreads = Integer.parseInt(args[i + 1]);
g_exec = Executors.newFixedThreadPool(m_nThreads);
i += 2;
// use BEAST environment variable to set Beast directories as colon separated list
// } else if (args[i].equals("-beastlib")) {
// ClassDiscovery.setJarPath(args[i + 1]);
// i += 2;
} else if (args[i].equals("-prefix")) {
System.setProperty("file.name.prefix", args[i + 1].trim());
i += 2;
} else if (args[i].equals("-strictversions")) {
useStrictVersions = true;
i += 1;
}
if (i == old) {
if (i == args.length - 1) {
beastFile = new File(args[i]);
i++;
} else {
throw new IllegalArgumentException("Wrong argument");
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException("Error parsing command line arguments: " + Arrays.toString(args) + "\nArguments ignored\n\n" + getUsage());
}
if (beastFile == null) {
// Not resuming so get starting options...
List<String> MCMCargs = new ArrayList<>();
Version version = new BEASTVersion2();
String titleString = "<html><center><p>Bayesian Evolutionary Analysis Sampling Trees<br>" +
"Version " + version.getVersionString() + ", " + version.getDateString() + "</p></center></html>";
javax.swing.Icon icon = IconUtils.getIcon(BeastMain.class, "images/beast.png");
String nameString = "BEAST " + version.getVersionString();
BeastDialog dialog = new BeastDialog(new JFrame(), titleString, icon);
if (!dialog.showDialog(nameString, m_nSeed)) {
return;
}
switch (dialog.getLogginMode()) {
case 0:/* do not ovewrite */
break;
case 1:
MCMCargs.add("-overwrite");
break;
case 2:
MCMCargs.add("-resume");
break;
}
MCMCargs.add("-seed");
MCMCargs.add(dialog.getSeed() + "");
if (dialog.getThreadPoolSize() > 0) {
MCMCargs.add("-threads");
MCMCargs.add(dialog.getThreadPoolSize() + "");
}
boolean useBeagle = dialog.useBeagle();
boolean beagleShowInfo = false;
long beagleFlags = 0;
if (useBeagle) {
beagleShowInfo = dialog.showBeagleInfo();
if (dialog.preferBeagleCPU()) {
beagleFlags |= BeagleFlag.PROCESSOR_CPU.getMask();
}
if (dialog.preferBeagleSSE()) {
beagleFlags |= BeagleFlag.VECTOR_SSE.getMask();
}
if (dialog.preferBeagleGPU()) {
beagleFlags |= BeagleFlag.PROCESSOR_GPU.getMask();
}
if (dialog.preferBeagleDouble()) {
beagleFlags |= BeagleFlag.PRECISION_DOUBLE.getMask();
}
if (dialog.preferBeagleSingle()) {
beagleFlags |= BeagleFlag.PRECISION_SINGLE.getMask();
}
}
if (beagleFlags != 0) {
System.setProperty("beagle.preferred.flags", Long.toString(beagleFlags));
}
if (!useBeagle) {
System.setProperty("java.only", "true");
}
File inputFile = dialog.getInputFile();
if (!beagleShowInfo && inputFile == null) {
System.err.println("No input file specified");
System.exit(1);
}
MCMCargs.add(inputFile.getAbsolutePath());
// BeastStartDialog dlg = new BeastStartDialog();
// if (dlg.m_bOK) {
// parseArgs(dlg.getArgs());
// }
parseArgs(MCMCargs.toArray(new String[0]));
return;
}
Log.warning.println("File: " + beastFile.getName() + " seed: " + m_nSeed + " threads: " + m_nThreads);
if (resume) {
Log.info.println("Resuming from file");
}
if (useStrictVersions) {
// grab "required" attribute from beast spec
if (beastFile.getPath().toLowerCase().endsWith(".json")) {
throw new IllegalArgumentException("The -strictversions flag is not implemented for JSON files yet (only XML files are supported).");
} else {
BufferedReader fin = new BufferedReader(new FileReader(beastFile));
StringBuffer buf = new StringBuffer();
String str = null;
int lineCount = 0;
while (fin.ready() && lineCount < 100) {
str = fin.readLine();
buf.append(str);
buf.append(' ');
}
fin.close();
str = buf.toString();
int start = str.indexOf("required=");
if (start < 0) {
throw new IllegalArgumentException("Could not find a 'required' attribute in the XML. Add the required attribute, or run without the -strictversions flag");
}
char c = str.charAt(start + 9);
start += 10;
int end = str.indexOf(c, start);
String packages = str.substring(start, end);
AddOnManager.loadExternalJars(packages);
}
} else {
AddOnManager.loadExternalJars();
}
// parse xml
Randomizer.setSeed(m_nSeed);
if (beastFile.getPath().toLowerCase().endsWith(".json")) {
m_runnable = new JSONParser().parseFile(beastFile);
} else {
try {
m_runnable = new XMLParser().parseFile(beastFile);
} catch (SAXException | ParserConfigurationException e) {
throw new IllegalArgumentException(e);
}
}
m_runnable.setStateFile(beastFile.getName() + ".state", resume);
} // parseArgs
public static String getUsage() {
return "Usage: BeastMCMC [options] <Beast.xml>\n" +
"where <Beast.xml> the name of a file specifying a Beast run\n" +
"and the following options are allowed:\n" +
"-resume : read state that was stored at the end of the last run from file and append log file\n" +
"-overwrite : overwrite existing log files (if any). By default, existing files will not be overwritten.\n" +
"-seed [<int>|random] : sets random number seed (default 127), or picks a random seed\n" +
"-threads <int> : sets number of threads (default 1)\n" +
"-prefix <name> : use name as prefix for all log files\n" +
"-beastlib <path> : Colon separated list of directories. All jar files in the path are loaded. (default 'beastlib')";
} // getUsage
/**
* open file dialog for prompting the user to specify an xml script file to process *
*/
String getFileNameByDialog() {
JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
fc.addChoosableFileFilter(new FileFilter() {
@Override
public boolean accept(File f) {
if (f.isDirectory()) {
return true;
}
String name = f.getName().toLowerCase();
if (name.endsWith(".xml")) {
return true;
}
return false;
}
// The description of this filter
@Override
public String getDescription() {
return "xml files";
}
});
fc.setDialogTitle("Load xml file");
int rval = fc.showOpenDialog(null);
if (rval == JFileChooser.APPROVE_OPTION) {
return fc.getSelectedFile().toString();
}
return null;
} // getFileNameByDialog
public void run() throws Exception {
g_exec = Executors.newFixedThreadPool(m_nThreads);
m_runnable.run();
g_exec.shutdown();
g_exec.shutdownNow();
} // run
/**
* class for starting Beast with a dialog *
*/
class BeastStartDialog extends JDialog {
private static final long serialVersionUID = 1L;
boolean m_bOK = false;
JTextField m_fileEntry;
JTextField m_seedEntry;
JCheckBox m_bUseGPU;
JComboBox<String> m_mode;
public BeastStartDialog() {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(DEFAULT_MODALITY_TYPE);
init();
setVisible(true);
}
String[] getArgs() {
List<String> args = new ArrayList<>();
args.add("-seed");
args.add(m_seedEntry.getText());
switch (m_mode.getSelectedIndex()) {
case 0:
break;
case 1:
args.add("-overwrite");
break;
case 2:
args.add("-resume");
break;
}
// if (m_bUseGPU.isSelected()) {
// args.add("-useGPU");
// }
args.add(m_fileEntry.getText());
return args.toArray(new String[0]);
}
void init() {
try {
setTitle("Beast Start Dialog");
Box box = Box.createVerticalBox();
box.add(createHeader());
box.add(Box.createVerticalStrut(10));
box.add(createFileInput());
box.add(Box.createVerticalStrut(10));
box.add(Box.createVerticalBox());
box.add(Box.createVerticalStrut(10));
box.add(createSeedInput());
// box.add(Box.createVerticalStrut(10));
// box.add(createBeagleInput());
box.add(Box.createVerticalStrut(10));
box.add(createModeInput());
box.add(Box.createVerticalGlue());
box.add(createRunQuitButtons());
add(box);
int size = UIManager.getFont("Label.font").getSize();
setSize(600 * size / 13, 500 * size / 13);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, "Could not create dialog: " + e.getMessage());
}
} // BeastStartDialog::init
private Component createHeader() {
Box box = Box.createHorizontalBox();
String iconLocation = "beast/app/draw/icons/beast.png";
ImageIcon icon = null;
try {
URL url = ClassLoader.getSystemResource(iconLocation);
if (url == null) {
System.err.println("Cannot find icon " + iconLocation);
return null;
}
icon = new ImageIcon(url);
} catch (Exception e) {
System.err.println("Cannot load icon " + iconLocation + " " + e.getMessage());
return null;
}
JLabel label = new JLabel(icon);
label.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
box.add(label, BorderLayout.WEST);
label = new JLabel("<html><center>BEAST<br>Version: " + VERSION + "<br>Developers: " + DEVELOPERS + "<br>Copyright: " + COPYRIGHT + "</html>");
label.setHorizontalAlignment(SwingConstants.CENTER);
box.add(label);
return box;
} // BeastStartDialog::createHeader
private Component createFileInput() {
Box box = Box.createHorizontalBox();
box.add(new JLabel("Beast XML File: "));
m_fileEntry = new JTextField();
int fontsize = m_fileEntry.getFont().getSize();
Dimension size = new Dimension(300 * fontsize / 13, 20 * fontsize / 13);
m_fileEntry.setMinimumSize(size);
m_fileEntry.setPreferredSize(size);
m_fileEntry.setSize(size);
m_fileEntry.setToolTipText("Enter file name of Beast 2 XML file");
m_fileEntry.setMaximumSize(new Dimension(1024 * fontsize / 13, 20 * fontsize / 13));
box.add(m_fileEntry);
//box.add(Box.createHorizontalGlue());
JButton button = new JButton("Choose file");
button.addActionListener(e -> {
JFileChooser fileChooser = new JFileChooser(Beauti.g_sDir);
File file = new File(m_fileEntry.getText());
if (file.exists())
fileChooser.setSelectedFile(file);
fileChooser.addChoosableFileFilter(new ExtensionFileFilter(".xml", "Beast xml file (*.xml)"));
fileChooser.setDialogTitle("Select Beast 2 XML file");
int rval = fileChooser.showOpenDialog(null);
if (rval == JFileChooser.APPROVE_OPTION) {
String fileName = fileChooser.getSelectedFile().toString();
if (fileName.lastIndexOf('/') > 0) {
Beauti.g_sDir = fileName.substring(0, fileName.lastIndexOf('/'));
}
m_fileEntry.setText(fileName);
}
});
box.add(button);
return box;
} // BeastStartDialog::createFileInput
private Component createSeedInput() {
Box box = Box.createHorizontalBox();
box.add(new JLabel("Random number seed: "));
m_seedEntry = new JTextField("127");
m_seedEntry.setHorizontalAlignment(SwingConstants.RIGHT);
Dimension size = new Dimension(100, 20);
m_seedEntry.setMinimumSize(size);
m_seedEntry.setPreferredSize(size);
m_seedEntry.setSize(size);
m_seedEntry.setToolTipText("Enter seed number used for initialising the random number generator");
int fontsize = m_seedEntry.getFont().getSize();
m_seedEntry.setMaximumSize(new Dimension(1024 * fontsize / 13, 20 * fontsize / 13));
box.add(m_seedEntry);
box.add(Box.createHorizontalGlue());
return box;
} // BeastStartDialog::createSeedInput
private Component createModeInput() {
Box box = Box.createHorizontalBox();
box.add(new JLabel("Mode of running: "));
m_mode = new JComboBox<>(new String[]{"default: only write new log files",
"overwrite: overwrite log files",
"resume: appends log to existing files (if any)"});
Dimension size = new Dimension(350, 20);
m_mode.setMinimumSize(size);
m_mode.setPreferredSize(size);
m_mode.setSize(size);
m_mode.setMaximumSize(size);
m_mode.setSelectedIndex(0);
box.add(m_mode);
box.add(Box.createHorizontalGlue());
return box;
} // BeastStartDialog::createModeInput
Component createRunQuitButtons() {
Box cancelOkBox = Box.createHorizontalBox();
cancelOkBox.setBorder(new EtchedBorder());
JButton okButton = new JButton("Run");
okButton.addActionListener(e -> {
m_bOK = true;
dispose();
});
JButton cancelButton = new JButton("Quit");
cancelButton.addActionListener(e -> {
dispose();
System.exit(0);
});
cancelOkBox.add(Box.createHorizontalGlue());
cancelOkBox.add(cancelButton);
cancelOkBox.add(Box.createHorizontalStrut(20));
cancelOkBox.add(okButton);
cancelOkBox.add(Box.createHorizontalStrut(20));
return cancelOkBox;
} // BeastStartDialog::createRunQuitButtons
} // class BeastStartDialog
public static void main(String[] args) {
try {
System.setProperty("beast.debug", "true");
BeastMCMC app = new BeastMCMC();
app.parseArgs(args);
app.run();
} catch (XMLParserException e) {
System.out.println(e.getMessage());
//e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
System.out.println(BeastMCMC.getUsage());
}
if (System.getProperty("beast.useWindow") == null) {
// this indicates no window is open
System.exit(0);
}
} // main
} // BeastMCMC