/*
Copyright (C) 2011 Diego Darriba, David Posada
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package es.uvigo.darwin.jmodeltest.exe;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import es.uvigo.darwin.jmodeltest.ApplicationOptions;
import es.uvigo.darwin.jmodeltest.ModelTest;
import es.uvigo.darwin.jmodeltest.ModelTestConfiguration;
import es.uvigo.darwin.jmodeltest.io.TextOutputStream;
import es.uvigo.darwin.jmodeltest.model.Model;
import es.uvigo.darwin.jmodeltest.model.ModelComparator;
import es.uvigo.darwin.jmodeltest.observer.ProgressInfo;
import es.uvigo.darwin.jmodeltest.utilities.Utilities;
/**
* RunPhyml.java
*
* Description: Makes phyml calculate likelihood scores for competing models
*
* @author Diego Darriba, University of Vigo / University of A Coruna, Spain
* ddarriba@udc.es
* @author David Posada, University of Vigo, Spain dposada@uvigo.es |
* darwin.uvigo.es
* @version 2.1 (May 2012)
*/
public abstract class RunPhyml extends Observable implements Observer {
// Set of variables for tuning the guided search algorithm
private static final boolean filterFrequencies = true;
private static final boolean filterRateMatrix = true;
private static final boolean filterRateVariation = true;
protected ApplicationOptions options;
protected Model[] models;
protected Model gtrModel = null;
public static final String[] COMPATIBLE_VERSIONS = {
"20130103", "20131022",
"20141009", "20141029",
"20150501", "20151222"};
public static String PHYML_VERSION = "3.0";
public static String PHYML_TREE_SUFFIX = "_phyml_tree_";
public static String PHYML_STATS_SUFFIX = "_phyml_stats_";
public static File phymlBinary;
public static String phymlBinaryStr;
private static String CURRENT_DIRECTORY = ModelTestConfiguration.PATH;
private static boolean PHYML_GLOBAL = ModelTestConfiguration.isGlobalPhymlBinary();
public static String PHYML_PATH = CURRENT_DIRECTORY + "exe/phyml/";
private static boolean compatiblePhyml = false;
protected Observer progress;
public static boolean isCompatible() {
return compatiblePhyml;
}
public static boolean checkBinary() {
boolean canExecute = false;
if (!ModelTestConfiguration.isGlobalPhymlBinary()) {
if (!RunPhyml.phymlBinary.exists()) {
if (ModelTest.MPJ_ME == 0)
Utilities.printRed("ERROR: PhyML binary cannot be found: "
+ RunPhyml.phymlBinary.getAbsolutePath() + "\n");
} else if (!RunPhyml.phymlBinary.canExecute()) {
if (ModelTest.MPJ_ME == 0)
Utilities.printRed(
"ERROR: PhyML binary exists, but it cannot be executed: "
+ RunPhyml.phymlBinary.getAbsolutePath() + "\n");
} else if (!RunPhyml.isCompatible()) {
if (ModelTest.MPJ_ME == 0) {
Utilities.printRed(
"WARNING: PhyML binary is not in the list of compatibility: \n");
Utilities.printRed(RunPhyml.phymlBinary.getAbsolutePath() + " v"
+ RunPhyml.PHYML_VERSION + "\n");
Utilities.printRed("Compatible versions: ");
for (int i = 0; i < RunPhyml.COMPATIBLE_VERSIONS.length; i++)
Utilities.printBlue(RunPhyml.COMPATIBLE_VERSIONS[i] + " ");
Utilities.printBlue("\n");
Utilities.printRed(
"jModelTest will try to continue execution anyway, but it might fail.\n");
}
canExecute = true;
} else {
if (ModelTest.MPJ_ME == 0)
Utilities.printBlue(
"PhyML binary: " + RunPhyml.phymlBinary.getAbsolutePath() + " v"
+ RunPhyml.PHYML_VERSION + "\n");
canExecute = true;
}
}
return canExecute;
}
private static boolean checkPhymlCompatibility(String binary) {
boolean binaryFound = false;
if (Utilities.findCurrentOS() != Utilities.OS_LINUX)
return false;
// get process and execute command line
String cmd[] = {binary, "--version"};
Runtime rt = Runtime.getRuntime();
Process proc;
try {
proc = rt.exec(cmd, null, null);
InputStream stdin = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdin);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ( (line = br.readLine()) != null) {
if (line.toLowerCase().contains("phyml version")) {
String[] linesplit = line.trim().replace(".", "").split(" ");
String version = linesplit[linesplit.length - 1];
PHYML_VERSION = version;
if (Arrays.asList(COMPATIBLE_VERSIONS).contains(version)) {
compatiblePhyml = true;
} else {
compatiblePhyml = false;
}
binaryFound = true;
}
}
br.close();
isr.close();
stdin.close();
proc.destroy();
} catch (IOException e) {
return false;
}
return binaryFound;
}
public static boolean isPhymlGlobal() {
return PHYML_GLOBAL;
}
static {
if (PHYML_GLOBAL) {
PHYML_PATH = "";
phymlBinaryStr = "phyml";
} else {
/* check the local paths */
String path = ModelTestConfiguration.getExeDir();
if (!path.startsWith(File.separator)) {
PHYML_PATH = CURRENT_DIRECTORY + File.separator + path;
} else {
PHYML_PATH = path;
}
if (!PHYML_PATH.endsWith(File.separator)) {
PHYML_PATH += File.separator;
}
phymlBinary = new File(PHYML_PATH + "phyml");
if (phymlBinary.exists() && phymlBinary.canExecute()) {
phymlBinaryStr = phymlBinary.getAbsolutePath();
compatiblePhyml = true;
} else {
phymlBinaryStr = PHYML_PATH + Utilities.getBinaryVersion();
}
/* Check if binary exists and is compatible */
phymlBinary = new File(phymlBinaryStr);
if (!checkPhymlCompatibility(phymlBinaryStr)) {
/* Check for system wide PhyML */
if (!(phymlBinary.exists() && phymlBinary.canExecute()))
{
if(checkPhymlCompatibility("/usr/bin/phyml")) {
PHYML_PATH = "/usr/bin";
phymlBinary = new File(PHYML_PATH + "/phyml");
phymlBinaryStr = phymlBinary.getAbsolutePath();
}
}
}
}
}
public RunPhyml(Observer progress, ApplicationOptions options,
Model[] models) {
if (models != null)
this.models = new Model[models.length];
else
this.models = new Model[0];
for (int i = 0; i < this.models.length; i++)
this.models[i] = models[i];
this.options = options;
this.progress = progress;
this.addObserver(progress);
Arrays.sort(this.models, new ModelComparator());
}
public void execute() {
// remove stuff from exe directories before starting
deleteFiles();
printSettings(ModelTest.getMainConsole());
// locate GTR model
String searchFor;
int gtrParams;
if (options.doI && options.doG) {
searchFor="GTR+I+G";
gtrParams = 10;
} else if(options.doI) {
searchFor="GTR+I";
gtrParams = 9;
} else if(options.doG) {
searchFor="GTR+G";
gtrParams = 9;
} else {
searchFor="GTR";
gtrParams = 8;
}
for (int i = (models.length - 1); i >= 0; i--) {
if (models[i].getName().startsWith(searchFor)) {
gtrModel = models[i];
break;
}
}
if (gtrModel == null) {
gtrModel = new Model(0, searchFor, "012345", gtrParams, false, false, false, true, options.doI, options.doG, 2, 4);
}
// estimate a NJ-JC tree if needed
if (options.fixedTopology) {
Model jcModel = null;
for (Model model : models) {
if (model.getName().equals("JC")) {
jcModel = model;
break;
}
}
if (jcModel != null) {
notifyObservers(ProgressInfo.BASE_TREE_INIT, 0, jcModel, null);
PhymlSingleModel jcModelPhyml = new PhymlSingleModel(jcModel,
0, true, false, options);
jcModelPhyml.addObserver(this);
jcModelPhyml.run();
// create JCtree file
TextOutputStream JCtreeFile = new TextOutputStream(options
.getTreeFile().getAbsolutePath(), false);
JCtreeFile.print(jcModel.getTreeString() + "\n");
JCtreeFile.close();
notifyObservers(ProgressInfo.BASE_TREE_COMPUTED, 0, jcModel,
null);
}
}
if (options.isGuidedSearch()) {
if (gtrModel != null) {
// compute GTR model
notifyObservers(ProgressInfo.GTR_OPTIMIZATION_INIT, models.length, gtrModel, null);
PhymlSingleModel gtrPhymlModel = new PhymlSingleModel(
gtrModel, 0, false, false, options);
gtrPhymlModel.run();
notifyObservers(ProgressInfo.GTR_OPTIMIZATION_COMPLETED, models.length, gtrModel, null);
GuidedSearchManager gsm = new GuidedSearchManager(
options.getGuidedSearchThreshold(), gtrModel,
filterFrequencies, filterRateMatrix,
filterRateVariation);
models = gsm.filterModels(models);
ModelTest.setCandidateModels(models);
} else {
notifyObservers(ProgressInfo.GTR_NOT_FOUND, models.length, models[0], null);
}
}
// compute likelihood scores for all models
notifyObservers(ProgressInfo.OPTIMIZATION_INIT, 0, models[0], null);
doPhyml();
}
public void executeIgnoreGaps(Model[] models) {
notifyObservers(ProgressInfo.REOPTIMIZATION_INIT, models.length, models[0], null);
parallelExecute(models, true);
notifyObservers(ProgressInfo.REOPTIMIZATION_COMPLETED, models.length, null, null);
}
protected boolean parallelExecute(Model models[], boolean ignoreGaps) {
ExecutorService threadPool = Executors.newFixedThreadPool(options
.getNumberOfThreads());
Collection<Callable<Object>> c = new ArrayList<Callable<Object>>();
int current = 0;
for (Model model : models) {
if (model != null) {
PhymlSingleModel psm = new PhymlSingleModel(model, current, false,
ignoreGaps, options);
psm.addObserver(this);
c.add(Executors.callable(psm));
current++;
}
}
Collection<Future<Object>> futures = null;
try {
futures = threadPool.invokeAll(c);
} catch (InterruptedException e) {
notifyObservers(ProgressInfo.INTERRUPTED, 0, null, null);
}
if (futures != null) {
for (Future<Object> f : futures) {
try {
f.get();
} catch (InterruptedException ex) {
notifyObservers(ProgressInfo.INTERRUPTED, 0, null, null);
ex.printStackTrace();
return false;
} catch (ExecutionException ex) {
// Internal exception while computing model.
// Let's continue with errors
ex.printStackTrace();
return false;
}
}
}
return true;
}
/***************************
* printSettings ***************************** * Prints the settings for the
* likelihood calculation * * *
***********************************************************************/
protected void printSettings(TextOutputStream stream) {
stream.println(" ");
stream.println(" ");
stream.println("---------------------------------------------------------------");
stream.println("* *");
stream.println("* COMPUTATION OF LIKELIHOOD SCORES WITH PHYML *");
stream.println("* *");
stream.println("---------------------------------------------------------------");
stream.println(" ");
stream.println("::Settings::");
stream.println(" ");
stream.println(" Phyml version = " + PHYML_VERSION);
stream.println(" Phyml binary = " + phymlBinary.getName());
stream.println(" Phyml path = " + phymlBinary.getAbsolutePath()
.substring(0, phymlBinary.getAbsolutePath().lastIndexOf(File.separator)) + File.separator);
stream.println(" Candidate models = " + models.length);
stream.print(" number of substitution schemes = ");
if (options.getSubstTypeCode() == 0)
stream.println("3");
else if (options.getSubstTypeCode() == 1)
stream.println("5");
else if (options.getSubstTypeCode() == 2)
stream.println("7");
else
stream.println("11");
if (options.doF)
stream.println(" including models with equal/unequal base frequencies (+F)");
else
stream.println(" including only models with equal base frequencies");
if (options.doI)
stream.println(" including models with/without a proportion of invariable sites (+I)");
else
stream.println(" including only models without a proportion of invariable sites");
if (options.doG)
stream.println(" including models with/without rate variation among sites (+G)"
+ " (nCat = " + options.numGammaCat + ")");
else
stream.println(" including only models without rate variation among sites");
stream.print(" Optimized free parameters (K) =");
stream.print(" substitution parameters");
if (options.countBLasParameters)
stream.print(" + " + options.getNumBranches() + " branch lengths");
if (options.optimizeMLTopology)
stream.print(" + topology");
stream.println(" ");
stream.print(" Base tree for likelihood calculations = ");
if (options.userTopologyExists) {
stream.println("fixed user tree topology.");
stream.println(" ");
stream.print("User tree " + "("
+ options.getInputTreeFile().getName() + ") = ");
stream.println(options.getUserTree());
stream.println(" ");
} else if (options.fixedTopology) {
stream.println("fixed BIONJ-JC tree topology");
} else if (options.optimizeMLTopology) {
stream.println("ML tree");
} else {
stream.println("BIONJ tree");
}
if (options.optimizeMLTopology) {
stream.print(" Tree topology search operation = ");
switch (options.treeSearchOperations) {
case NNI:
stream.println("NNI");
break;
case SPR:
stream.println("SPR");
break;
case BEST:
stream.println("BEST");
break;
}
}
if (options.isClusteringSearch()) {
stream.println(" Using hill-climbing hierarchical clustering");
}
if (options.isGuidedSearch()) {
stream.println(" Using heuristic model filtering ");
}
stream.println(" ");
}
protected abstract Object doPhyml();
/***********************************************************************
* interruptThread
*
* Interrupts the calculation of likelihood scores
***********************************************************************/
public void interruptThread() {
notifyObservers(ProgressInfo.OPTIMIZATION_COMPLETED_INTERRUPTED, 0,
null, null);
// workerPhyml.interrupt();
// IS_INTERRUPTED = true;
}
private void deleteFiles() {
/* phymlFolder */
//if (options.getLogFile() != null)
options.getLogFile().delete();
}
protected void notifyObservers(int type, int value, Model model,
String message) {
setChanged();
notifyObservers(new ProgressInfo(type, value, model, message));
}
@Override
public void update(Observable o, Object arg) {
setChanged();
notifyObservers(arg);
}
} // class RunPhyml