/*
* Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fhcrc.cpl.toolbox.statistics;
import org.apache.log4j.Logger;
import org.fhcrc.cpl.toolbox.ApplicationContext;
import org.fhcrc.cpl.toolbox.filehandler.TempFileManager;
import java.io.*;
import java.util.*;
/**
* A class with generic methods for interfacing with R. Hides all kinds of exceptions, both
* from R and from file IO
*/
public class RInterface
{
private static Logger _log = Logger.getLogger(RInterface.class);
protected static int expressionNumber = 0;
//maximum number of milliseconds to wait for a response from R before giving up
protected static final int DEFAULT_MAX_R_WAIT_MILLIS = 5000;
//number of milliseconds to sleep between checks for response from R
protected static final int R_SLEEP_INCREMENT_MILLIS = 50;
/**
* Choose the right name of the command for running R for this OS
* @return
*/
protected static String getRCommandStringForOS()
{
String os = System.getProperty("os.name");
String cmd;
if (os.contains("Windows"))
cmd = "RCMD";
else
cmd = "R CMD";
return cmd;
}
/**
* Run an R script, setting the R directory to be the temp dir.
* Also mark the Rout file generated by R for deletion when tempfiles for the caller
* are cleaned up (don't want to do it here because a user might want to view the Rout file).
*
* This is the easy and stable way to invoke R. The alternative is to interact with
* the input, output, and error streams of the process, which provides some advantages.
* @param rScriptFile
* @return true if successful, false otherwise
*/
public static boolean runRScript(File rScriptFile, Object caller)
throws RuntimeException
{
//assume failure
boolean success = false;
String rScriptFilepath = rScriptFile.getAbsolutePath();
try
{
String cmd = getRCommandStringForOS();
cmd = cmd + " BATCH --slave " + rScriptFilepath;
_log.debug("Before runing R, script file " + rScriptFilepath);
Process p = Runtime.getRuntime().exec(cmd ,null, TempFileManager.getTmpDir());
_log.debug("after running R");
int err = p.waitFor();
_log.debug("process returned, "+err);
if (err == 0)
success = true;
else
{
TempFileManager.unmarkFileForDeletion(rScriptFile, caller);
throw new RuntimeException("Error in executing R, temp dir is " + TempFileManager.getTmpDir() + ". R process status: " + err, null);
}
//Only try to delete the out file if we successfully ran the script
TempFileManager.markFileForDeletion(new File(rScriptFile.getAbsolutePath() + "out"), caller);
}
catch (Exception e)
{
throw new RuntimeException("Failed to run R code. Details follow.\n" +
"Please make sure that R is a) installed and b) on your PATH. To do this,\n" +
"open a command prompt window and type R (on Linux) or RCMD (on Windows)\n" +
"If the R command is not found, your PATH environment variable needs to be\n" +
"modified to contain the R binary directory.",e);
}
return success;
}
/**
* First index in response indicates row. Second indicates column.
* @param rResponse
* @return
*/
public static float[][] processRMatrixResponse(String rResponse)
{
String[] textLines = rResponse.split("\n");
int numRows = textLines.length-1;
String firstLine = textLines[0];
int numCols=0;
int lastWhitespaceFirstLine=0;
while (Character.isWhitespace(firstLine.charAt(lastWhitespaceFirstLine)))
lastWhitespaceFirstLine++;
firstLine=firstLine.substring(lastWhitespaceFirstLine);
String[] columnNames = firstLine.split("\\s+");
numCols = columnNames.length;
_log.debug("processRMatrixResponse, first line: " + firstLine + ", numCols=" + numCols);
float[][] result = new float[numRows][numCols];
for (int i=0; i<numRows; i++)
{
String line = textLines[i+1];
line=line.substring(line.indexOf("]")+1);
int lastWhitespace=0;
while (Character.isWhitespace(line.charAt(lastWhitespace)))
lastWhitespace++;
line=line.substring(lastWhitespace);
String[] entries = line.split("\\s+");
// int numEmpties=0;
// if (entries[0] == null || entries[0].length()==0)
// numEmpties++;
for (int j=0; j<numCols; j++)
{
try
{
if (entries[j].equals("NA"))
result[i][j] = Float.NaN;
else
result[i][j] = Float.parseFloat(entries[j]);
}
catch (RuntimeException e)
{
ApplicationContext.infoMessage("FAILED while processing line " + i + ": **" + line + "**, specifically the value **" + entries[j] + "**, entry " + j);
throw e;
}
}
}
return result;
}
/**
* cute little method that handles a n R coefficients() response by ignoring the first line
* and splitting the second around whitespace. Brittle, don't give it anything at all funky
* @param rResponse
* @return
*/
public static double[] processRCoefficientResponse(String rResponse)
throws NumberFormatException
{
_log.debug("Parsing R response:\n***\n"+rResponse+"\n***\n");
List<Float> resultList = new ArrayList<Float>();
String justTheNumbers = rResponse;
if (justTheNumbers.contains("\n"))
justTheNumbers = justTheNumbers.substring(rResponse.indexOf('\n'));
//R responses can get split over multiple lines. In this case, that means
//alternating lines of header, values
String[] textLines = rResponse.split("\n");
if (textLines.length > 2)
{
_log.debug("Multiple (" + textLines.length + ") lines in R response");
justTheNumbers = textLines[1];
for (int i=3; i<textLines.length; i+=2)
justTheNumbers = justTheNumbers + textLines[i];
}
_log.debug("just the numbers:\n***\n"+justTheNumbers+"\n***\n");
List<Float> coeffsThisLine = processCoefficientOnlyLine(justTheNumbers);
for (float coeff : coeffsThisLine)
resultList.add(coeff);
if (resultList.size() < 2)
throw new NumberFormatException("Problem parsing coefficient response from R");
double[] result = new double[resultList.size()];
for (int i=0; i<result.length; i++)
result[i] = resultList.get(i);
return result;
}
/**
* Parse the important bits of a response from R's 'coeff' command
* @param coefficientLine
* @return
*/
protected static List<Float> processCoefficientOnlyLine(String coefficientLine)
{
List<Float> result = new ArrayList<Float>();
String[] stringCoeffs = coefficientLine.split("\\s");
for (int i=0; i<stringCoeffs.length; i++)
{
_log.debug("@@@"+stringCoeffs[i]+"@@@");
if (stringCoeffs[i].length() > 0)
{
result.add(Float.parseFloat(stringCoeffs[i]));
}
}
return result;
}
/**
* Parse an R list result as a list of Floats
* @param varString
* @return
*/
public static List<Float> parseNumericList(String varString)
{
List<Float> result = new ArrayList<Float>();
String[] probChunks = varString.split("\\s");
for (String probChunk : probChunks)
{
if (!probChunk.contains("[") && probChunk.length() > 0)
{
result.add(Float.parseFloat(probChunk));
}
}
return result;
}
/**
* Given R output, extract a map from variable names provided by a "list" command, to the strings that
* contain the values
* @param listOutput
* @return
*/
public static Map<String, String> extractVariableStringsFromListOutput(String listOutput)
{
_log.debug("Extracting variable strings from list output...");
String[] lines = listOutput.split("\\n");
Map<String, String> result = new HashMap<String, String>();
StringBuffer currentVarBuf = null;
String currentVarName = null;
for (String line : lines)
{
line=line.trim();
if (line.startsWith("$"))
{
if (currentVarName != null)
{
_log.debug("extractVarStrings: found var " + currentVarName);
result.put(currentVarName, currentVarBuf.toString());
}
currentVarName = line.substring(1);
currentVarBuf = new StringBuffer();
}
else
{
if (currentVarName != null)
currentVarBuf.append("\n" + line);
}
}
result.put(currentVarName, currentVarBuf.toString());
return result;
}
/**
* Evaluate an R command, giving it the default amount of time to respond
* @param rCommand command to execute
* @return results from R
*/
public static String evaluateRCommand(String rCommand)
{
return evaluateRCommand(rCommand, DEFAULT_MAX_R_WAIT_MILLIS);
}
/**
* Talks to the ReaderThread to check periodically for the specified sentinel.
* TODO: make this more efficient. Right now, at every read, it pulls back the whole response
* @param readerThread
* @param endResponseSentinel
* @param maxMillisToWaitForResponse
* @return
* @throws IOException
*/
protected static String collectInput(RReaderThread readerThread,
Process p,
String endResponseSentinel,
int maxMillisToWaitForResponse)
throws IOException
{
int totalMillisSlept = 0;
String responseString = "";
// _log.debug("collectInput, millis to wait: " + maxMillisToWaitForResponse);
//loop until sentinel shows up. Likely we'll get it right away, but
//loop anyway just in case R is slow, which it can be
responseString = readerThread.getReadString();
//every time we get more back from R, we convert the whole thing to a String.
//This is wasteful, but it's necessary to make ABSOLUTELY SURE we capture
//the sentinel if it occurs. Besides, likely we won't do this more than once
//or maybe twice on typical commands
while (readerThread.status == RReaderThread.STATUS_READING &&
(endResponseSentinel == null ||
!(responseString).contains(endResponseSentinel)))
{
boolean processIsAlive = false;
int exitValue = 0;
try
{
exitValue = p.exitValue();
}
catch (IllegalThreadStateException itse)
{
processIsAlive = true;
}
if (!processIsAlive)
{
StringBuffer exceptionMessageBuf = new StringBuffer("R Process exited before done reading, with status " + exitValue + ".\n");
if (responseString.length() < 5000)
{
exceptionMessageBuf.append("Output from process: " + responseString);
}
else
{
exceptionMessageBuf.append("Output from process is too long to display (" +
responseString.length() + " chars)");
}
throw new IOException(exceptionMessageBuf.toString());
}
//System.err.println(" loop, " + responseString);
if (totalMillisSlept > maxMillisToWaitForResponse)
{
break;
}
//sleep for one increment
try
{
Thread.sleep(R_SLEEP_INCREMENT_MILLIS);
totalMillisSlept += R_SLEEP_INCREMENT_MILLIS;
}
catch (InterruptedException e)
{
}
responseString = readerThread.getReadString();
}
_log.debug("collectInput, readerThread status: " + readerThread.status);
if (readerThread.status == RReaderThread.STATUS_ERROR)
throw readerThread.exception;
return responseString;
}
/**
* Handles a single write of a byte array to R
*/
public static class RWriterThread extends Thread
{
protected OutputStream out = null;
protected BufferedOutputStream internalOut;
protected byte[] bytes = null;
public boolean done = false;
public RWriterThread(DataOutputStream out, byte[] bytes)
{
this.out = out;
this.bytes = bytes;
}
public void run()
{
try
{
for (int i = 0; i < bytes.length; i++)
{
out.write(bytes[i]);
out.flush();
}
} catch (Throwable t)
{
t.printStackTrace(System.out);
}
done = true;
}
}
/**
* Manages the R error inputstream.
* On some systems, the error inputstream must be read periodically or writing to the
* outputstream will hang. In particular, I've seen output writing hang after 8kb
* are written; apparently something gets written to the error outputstream during that
* write, and unless it's written, nothing doing.
*/
public static class RErrorReaderThread extends Thread
{
protected InputStream inputStream = null;
protected StringBuffer accumulatedResponse = new StringBuffer();
protected StringBuffer newDataBuf = new StringBuffer();
protected boolean keepReading = true;
public IOException exception = null;
public int status = STATUS_READING;
public static final int STATUS_READING = 0;
public static final int STATUS_ERROR = 1;
protected boolean hasNewData = false;
public RErrorReaderThread(Process p)
{
inputStream = new BufferedInputStream(p.getErrorStream());
}
public void run()
{
try
{
_log.debug("Starting error thread");
while(keepReading)
{
int currentBytesAvailable = inputStream.available();
if (currentBytesAvailable > 0)
{
byte[] rResponse = new byte[currentBytesAvailable];
inputStream.read(rResponse);
_log.debug("R ERROR reader got output: " + new String(rResponse));
accumulatedResponse.append(new String(rResponse));
hasNewData = true;
newDataBuf.append(new String(rResponse));
}
//sleep for one increment
try
{
Thread.sleep(R_SLEEP_INCREMENT_MILLIS);
}
catch (InterruptedException e)
{
_log.debug("error thread interrupted");
}
}
if (inputStream != null)
inputStream.close();
_log.debug("Error reader successfully shutdown");
}
catch (IOException e)
{
_log.error("Failure while reading R response", e);
status = STATUS_ERROR;
exception = e;
}
finally
{
try
{
if (inputStream != null)
inputStream.close();
}
catch (Exception e)
{
}
}
}
public void shutdown()
{
keepReading = false;
}
public String getReadString()
{
return accumulatedResponse.toString();
}
public boolean hasNewData()
{
return hasNewData;
}
public String getNewData()
{
String result = newDataBuf.toString();
newDataBuf = new StringBuffer();
return result;
}
}
/**
* Latches onto the R process' input stream and doesn't let go
* TODO: provide a way to get access to just whatever was read since last time you checked
*/
public static class RReaderThread extends Thread
{
InputStream inputStream = null;
StringBuffer accumulatedResponse = new StringBuffer();
protected boolean keepReading = true;
public int totalMillisSlept = 0;
public IOException exception = null;
public int status = STATUS_READING;
public static final int STATUS_READING = 0;
public static final int STATUS_ERROR = 1;
public RReaderThread(Process p)
{
inputStream = p.getInputStream();
}
public void run()
{
try
{
_log.debug("Starting R output reader thread");
while(keepReading)
{
int currentBytesAvailable = inputStream.available();
if (currentBytesAvailable > 0)
{
byte[] rResponse = new byte[currentBytesAvailable];
inputStream.read(rResponse);
String responseString = new String(rResponse);
_log.debug("R output reader got output: " + responseString);
accumulatedResponse.append(responseString);
//System.err.println(new String(rResponse));
}
//sleep for one increment
try
{
Thread.sleep(R_SLEEP_INCREMENT_MILLIS);
totalMillisSlept += R_SLEEP_INCREMENT_MILLIS;
}
catch (InterruptedException e)
{
}
}
if (inputStream != null)
inputStream.close();
_log.debug("Reader successfully shutdown");
}
catch (IOException e)
{
_log.error("Failure while reading R response", e);
status = STATUS_ERROR;
exception = e;
}
}
public void shutdown()
{
keepReading = false;
}
public String getReadString()
{
return accumulatedResponse.toString();
}
}
/**
* Cover method to start up a writer thread, send it some bytes, and make sure they got written
* @param rOut
* @param bytesToWrite
* @throws IOException
*/
public static void writeToR(DataOutputStream rOut, byte[] bytesToWrite)
throws IOException
{
RWriterThread wt = new RWriterThread(rOut, bytesToWrite);
wt.start();
while (!wt.done)
{
try
{
Thread.sleep(15);
}
catch(InterruptedException ie)
{
}
}
}
/**
* Read in a fully-qualified resource on the classpath, i.e., an R script
* @param resourcePath
* @return
*/
public static String readResourceFile(String resourcePath)
throws IOException
{
_log.debug("readResourceFile, resourcePath: " + resourcePath);
InputStream in = RInterface.class.getResourceAsStream(resourcePath);
if (in == null)
throw new IOException("ERROR!! null resource!");
StringBuffer commandBuf = new StringBuffer();
int readchar;
while ((readchar = in.read()) != -1)
commandBuf.append((char) readchar);
return commandBuf.toString();
}
/**
* Evaluate an R command, or series of commands. Time out if we wait longer than maxMillis...
* Return a every single thing that R gives back, with whitespace trimmed from start and end.
* In order to make sure we wait the appropriate amount of time, and to make sure that we return
* only R's response and nothing else, I do the following:
*
* 1. Place a sentinel that will be echoed before the command response
* 2. Place a sentinel (that can be evaluated by R) after a newline, so that R will echo it after
* the command completes. That way we know to ignore everything after the second sentinel, and we know when
* we're done
* 3. When we get back the response, take everything between the two sentinel responses. Then, if that
* response contains any "Package loaded" lines, take them out, too
*
* If R fails for any reason, throw a RuntimeException
* @param rCommand
* @param maxMillisToWaitForResponse
* @return the result from R
*/
public static String evaluateRCommand(String rCommand, int maxMillisToWaitForResponse)
{
_log.debug("Running R command:");
// _log.debug(rCommand);
while(Character.isWhitespace(rCommand.charAt(rCommand.length()-1)))
rCommand = rCommand.substring(0, rCommand.length()-1);
long startTime = new Date().getTime();
String result = null;
String responseString = "";
boolean timedOut = false;
Process p = null;
DataOutputStream rOut = null;
RReaderThread responseReaderThread = null;
RErrorReaderThread errorReaderThread = null;
try
{
_log.debug("Starting R...");
String cmd = "R --vanilla --slave";
//Kick off R, set up the input and output streams, write the full command and sentinels
p = Runtime.getRuntime().exec(cmd, null, TempFileManager.getTmpDir());
//outputstream
rOut = new DataOutputStream(new BufferedOutputStream(p.getOutputStream(), 8000));
_log.debug("R process started.");
//this is necessary for Windows. R initially produces some output
//when you kick it off, and if you don't collect it Windows hangs
//eternally.
responseReaderThread = new RReaderThread(p);
responseReaderThread.start();
errorReaderThread = new RErrorReaderThread(p);
errorReaderThread.start();
byte[] bytesToR = rCommand.getBytes();
_log.debug("Sending command to R. " + bytesToR.length + " bytes....");
//System.err.println("****************");
//System.err.println(new String(bytesToR));
//System.err.println("****************");
String sentinel1 = "\"SENTINEL_SUPERCaliFRAGILIsticEXPIAlidOCIOUS1_SENTINEL\"";
String sentinel2 = "\"SENTINEL_SUPERCaliFRAGILIsticEXPIAlidOCIOUS2_SENTINEL\"";
writeToR(rOut, ("\n" + sentinel1 + '\n').getBytes());
writeToR(rOut, bytesToR);
writeToR(rOut, ("\n" + sentinel2 + '\n').getBytes());
_log.debug("Sent command to R.");
//read from the input stream until we come to the end-command sentinel,
//which will be after the echo of our input but before the response.
//Reduce the max time to wait by however long we've already waited.
_log.debug("Waiting for output...");
//read from the input stream until we come to the second sentinel,
//which will be echoed after we get our response.
//Reduce the max time to wait by however long we've already waited.
responseString = collectInput(responseReaderThread, p, sentinel2,
(int) ((maxMillisToWaitForResponse) -
(new Date().getTime() - startTime)));
if (responseString == null ||
!responseString.contains(sentinel2))
{
// _log.debug(responseString);
if (new Date().getTime() - startTime > maxMillisToWaitForResponse)
{
timedOut = true;
throw new RuntimeException("timed out");
}
else
{
throw new RuntimeException("unknown error, didn't get sentinel");
}
}
//System.err.println("Raw R response: " + responseString);
_log.debug("Got sentinel. Response length: " + responseString.length());
//at this point we've both sentinels.
//Get rid of the last line, which is the sentinel response
int startIndex = responseString.indexOf(sentinel1) + sentinel1.length();
while (Character.isWhitespace(responseString.charAt(startIndex)))
startIndex++;
int firstBadIndex = responseString.indexOf(sentinel2);
while (responseString.charAt(firstBadIndex) != '\n' && firstBadIndex > startIndex)
firstBadIndex--;
result = responseString.substring(startIndex, firstBadIndex);
//We may get "package loaded" or "null device" lines. If so, ignore them
while (result.startsWith("Package") || result.startsWith("null device"))
{
String firstLine = result.substring(0, result.indexOf("\n"));
if (result.startsWith("null device") || firstLine.contains("loaded."))
{
result = result.substring(firstLine.length() + 1);
}
else
break;
}
_log.debug("Important part of response (length " + result.length() + "), with whitespace: " + result);
//_log.debug(result);
//strip whitespace from beginning and end
while (result.length() > 0 && Character.isWhitespace(result.charAt(0)))
result = result.substring(1);
while (result.length() > 0 && Character.isWhitespace(result.charAt(result.length()-1)))
result = result.substring(0,result.length()-1);
_log.debug("Stripped whitespace from response");
}
catch (Exception e)
{
String failureReason = "";
if (timedOut)
{
failureReason = "Timed out while calling R. Max millis: " + maxMillisToWaitForResponse +
", waited " + (new Date().getTime() - startTime);
}
else
{
failureReason = "Error calling R, temp dir is " + TempFileManager.getTmpDir() + ". ";
//write out the R command that failed to a file
try
{
File commandOutFile =
TempFileManager.createTempFile("r_failed_command.txt", "RCOMMANDFAILUREFILE_DONOTDELETE");
PrintWriter commandOutPW = new PrintWriter(commandOutFile);
commandOutPW.println(rCommand);
commandOutPW.flush();
commandOutPW.close();
ApplicationContext.infoMessage("*******\nWrote failed R command to file " +
commandOutFile.getAbsolutePath() + "\n*******");
}
catch (Exception commandOutE)
{
_log.error("Failed to create R command output file", commandOutE);
}
try
{
if (responseReaderThread != null)
{
failureReason += failureReason + "R output:\n" +
collectInput(responseReaderThread, p, null, 100);
}
else
failureReason += "No error output from R to display. Error Message: " + e.getMessage() + ", Exception class: " + e.getClass().getName();
}
catch (Exception ex)
{
failureReason += "Failed while interrogating R error output";
}
finally
{
if (errorReaderThread.hasNewData())
failureReason = failureReason + "\n Error Output: " + errorReaderThread.getNewData();
e.printStackTrace(System.err);
}
}
throw new RuntimeException(failureReason,e);
}
finally
{
try
{
if (rOut != null)
{
tellRToQuit(rOut);
rOut.close();
//Give R a chance to shut down before destroying the process
_log.debug("Shutting down R...");
p.waitFor();
_log.debug("R successfully shutdown");
}
}
catch (Exception e)
{
_log.debug("Failed to shut down R properly.");
}
//close all the input and output streams
if (responseReaderThread != null)
responseReaderThread.shutdown();
if (errorReaderThread != null)
errorReaderThread.shutdown();
}
return result;
}
protected static void tellRToQuit(DataOutputStream rOut)
throws IOException
{
writeToR(rOut, "q(save=\"no\")\n".getBytes());
}
/**
* cover method with default wait time, vector variables only
* @param rExpression
* @param variableValues
* @param dependentPackageNames
* @return
*/
public static String evaluateRExpression(String rExpression,
Map<String, double[]> variableValues,
String[] dependentPackageNames)
{
return evaluateRExpression(rExpression, variableValues,
dependentPackageNames, DEFAULT_MAX_R_WAIT_MILLIS);
}
/**
* Generic method for running an R expression and feeding the output to a file.
* Populates variable values and loads packages if necessary
* @param rExpression
* @return
*/
public static String evaluateRExpression(String rExpression,
Map<String, double[]> variableValues,
String[] dependentPackageNames,
int maxMillisToWaitForResponse)
{
return evaluateRExpression(rExpression, variableValues, null,
dependentPackageNames, maxMillisToWaitForResponse);
}
/**
* Cover method with default wait time
* @param rExpression
* @param vectorVariableValues
* @param matrixVariableValues
* @param dependentPackageNames
* @return
*/
public static String evaluateRExpression(String rExpression,
Map<String, double[]> vectorVariableValues,
Map<String, double[][]> matrixVariableValues,
String[] dependentPackageNames)
{
return evaluateRExpression(rExpression, vectorVariableValues, matrixVariableValues,
dependentPackageNames, DEFAULT_MAX_R_WAIT_MILLIS);
}
public static String evaluateRExpression(String rExpression,
Map<String, double[]> vectorVariableValues,
Map<String, double[][]> matrixVariableValues,
String[] dependentPackageNames,
int maxMillisToWaitForResponse)
{
return evaluateRExpression(rExpression, null, vectorVariableValues, matrixVariableValues,
dependentPackageNames, maxMillisToWaitForResponse);
}
/**
* R doesn't like backslashes, even on Windows. So replace them with forward slashes
* @param file
* @return
*/
public static String generateRFriendlyPath(File file)
{
String filePath = file.getAbsolutePath();
return generateRFriendlyPath(filePath);
}
public static String generateRFriendlyPath(String filePath)
{
return filePath.replaceAll("\\\\","/");
}
/**
* Read rFile into a String and run the whole file as an R expression
* @param rFile
* @param scalarVariableValues
* @param vectorVariableValues
* @param matrixVariableValues
* @param dependentPackageNames
* @param maxMillisToWaitForResponse
* @return
* @throws FileNotFoundException
*/
public static String runRScript(File rFile,
Map<String, Object> scalarVariableValues,
Map<String, double[]> vectorVariableValues,
Map<String, double[][]> matrixVariableValues,
String[] dependentPackageNames,
int maxMillisToWaitForResponse)
throws FileNotFoundException
{
FileReader fr = new FileReader(rFile);
char[] fileArray = new char[(int)rFile.length()];
return evaluateRExpression(new String(fileArray), scalarVariableValues,
vectorVariableValues,
matrixVariableValues,
dependentPackageNames,
maxMillisToWaitForResponse);
}
/**
* Generic method for running an R expression and feeding the output to a file.
* Populates variable values and loads packages if necessary
* @param rExpression
* @return
*/
public static String evaluateRExpression(String rExpression,
Map<String, Object> scalarVariableValues,
Map<String, double[]> vectorVariableValues,
Map<String, double[][]> matrixVariableValues,
String[] dependentPackageNames,
int maxMillisToWaitForResponse)
{
StringBuffer rCommand = new StringBuffer();
if (dependentPackageNames != null && dependentPackageNames.length > 0)
{
for (String dependentPackageName : dependentPackageNames)
{
rCommand.append("library('" + dependentPackageName + "')\n");
}
}
if (scalarVariableValues != null && scalarVariableValues.size() > 0)
{
for (String variableName : scalarVariableValues.keySet())
{
rCommand.append(variableName + "<-" + scalarVariableValues.get(variableName) + "\n");
}
}
if (vectorVariableValues != null && vectorVariableValues.size() > 0)
{
for (String variableName : vectorVariableValues.keySet())
{
rCommand.append(variableName + "<-c(");
double[] thisVarValues = vectorVariableValues.get(variableName);
for (int i=0; i<thisVarValues.length; i++)
{
rCommand.append(Double.isNaN(thisVarValues[i]) ? "NA" : thisVarValues[i]);
if (i < thisVarValues.length-1)
rCommand.append(",\n");
}
rCommand.append(")\n");
}
}
if (matrixVariableValues != null && matrixVariableValues.size() > 0)
{
for (String variableName : matrixVariableValues.keySet())
{
rCommand.append(variableName + "<-matrix(c(");
double[][] thisVarValues = matrixVariableValues.get(variableName);
_log.debug("Building matrix " + variableName + ", " + thisVarValues[0].length + " columns, " +
thisVarValues.length + " rows");
for (int i=0; i<thisVarValues.length; i++)
{
for (int j=0; j < thisVarValues[0].length; j++)
{
rCommand.append(Double.isNaN(thisVarValues[i][j]) ? "NA" : thisVarValues[i][j]);
//append the comma unless we're on the very last cell
if (i < thisVarValues.length-1 ||
j < thisVarValues[0].length-1)
rCommand.append(",");
//this is just formatting, for debug purposes
if (j == thisVarValues[0].length-1)
rCommand.append("\n");
}
}
rCommand.append("),nrow=" + thisVarValues.length +
",ncol="+thisVarValues[0].length + ", byrow=TRUE)\n");
}
}
rCommand.append(rExpression);
return evaluateRCommand(rCommand.toString(), maxMillisToWaitForResponse);
}
/**
* Creates a file containing the values to be loaded into the R variable with name variableName,
* and returns the R syntax necessary to load the values into the variable.
* If not successful, throws an exception
* @param variableName
* @param variableValues
* @return
*/
/* This was useful when everything was file-based. Not so much any more
protected static String populateArrayValueFromFile(String variableName, double[] variableValues,
Object caller)
throws FileNotFoundException
{
File tempVarValueFile =
TempFileManager.createTempFile("RInterface.populateArrayValueFromFile.var." + variableName,
caller);
PrintWriter pw = null;
try
{
pw = new PrintWriter(tempVarValueFile);
for (double variableValue : variableValues)
pw.println(variableValue);
}
finally
{
if (pw != null)
pw.close();
}
return variableName + "<-read.table('" + tempVarValueFile.getAbsolutePath() +"',header=FALSE)[,1]";
}
*/
}