package at.ac.tuwien.iter.services.impl.matlab;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import matlabcontrol.LoggingMatlabProxy;
import matlabcontrol.MatlabConnectionException;
import matlabcontrol.MatlabInvocationException;
import matlabcontrol.MatlabProxyFactory;
import matlabcontrol.MatlabProxyFactoryOptions;
import matlabcontrol.extensions.MatlabNumericArray;
import matlabcontrol.extensions.MatlabTypeConverter;
import org.apache.tapestry5.ioc.services.RegistryShutdownHub;
import org.slf4j.Logger;
import at.ac.tuwien.iter.data.TestResult;
import at.ac.tuwien.iter.services.MathEngineDao;
/**
* This implementation uses matlabcontrol that must be put on the classpath.
*
* TODO I still need to figure out how to call everything, and how to structure
* organize the access to its objects.
*
* It uses the concept of sessions, but it is not multithreading-prone we
* declare this service as singleton ?
*
*
* THe documentation is available here:https://code.google.com/p/matlabcontrol/
* but they said also: Controlling the MATLAB GUI from Java This approach is for
* those that want to control a MATLAB session (or multiple sessions) that can
* also be used by a user. All of the solutions in this approach rely on the
* Java MATLAB Interface (JMI). matlabcontrol was created to control either a
* MATLAB session from within it, or one or more sessions of the MATLAB from a
* Java program not launched from within MATLAB. Those launched from Java are
* done without any need for user interaction: the user does not need to change
* MATLAB configurations, launch MATLAB, or type any special commands into
* MATLAB so that it can be controlled
*
*
* Instead we need:Controlling the MATLAB engine
*
*
*
*
* For the moment I kept the same structure as the OctaveImpl
*
* @author alessiogambi
*
*/
public class MatlabControlImpl implements MathEngineDao {
private Logger logger;
// /*
// * Various indices on the test executions
// */
// private Map<String, String> testExecutions; // test-execution-ID,
// // matlab-variable-name
//
// private List<String> criticalTestCases; // test-execution-ID
private LoggingMatlabProxy proxy;
private MatlabProxyFactory factory;
private MatlabTypeConverter processor;
private Object lock;
private int nParameters;
// Need a queued access to the session otherwise it opens several matlab ?
// FIXME Asking for the problem_size at this time is bad but I cannot think
// about anything else right now
public MatlabControlImpl(Logger logger, RegistryShutdownHub shutdownHub,
String gpmlDir, String iterDir, int problemSize, double tol,
double min_ei, double[] LB, double[] UB, int nBins,
String matlabLogFile) throws MatlabConnectionException,
MatlabInvocationException {
this.logger = logger;
logger.info("LB " + Arrays.toString(LB));
logger.info("UB " + Arrays.toString(UB));
MatlabProxyFactoryOptions.Builder builder = new MatlabProxyFactoryOptions.Builder();
builder.setHidden(true);
factory = new MatlabProxyFactory();
proxy = new LoggingMatlabProxy(factory.getProxy());
// Add shutdown hook to exit matlab once everything its over
// Use @PostInjection
shutdownHub.addRegistryShutdownListener(new Runnable() {
public void run() {
try {
if (proxy.isConnected()) {
proxy.exit();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
// Instantiate utils and other objects
processor = new MatlabTypeConverter(proxy);
// Load and setup GPML libraries
proxy.eval(String.format("addpath ('%s');", gpmlDir));
proxy.eval("startup;");
// Load and setup our libraries
proxy.eval(String.format("addpath ('%s');", iterDir));
double[][] _LB = new double[1][];
_LB[0] = LB;
processor.setNumericArray("LB", new MatlabNumericArray(_LB, null));
double[][] _UB = new double[1][];
_UB[0] = UB;
processor.setNumericArray("UB", new MatlabNumericArray(_UB, null));
// NOTE THAT STRINGs mist have ''
proxy.eval(String.format("startup_iter(%d,%f,%f,LB,UB,%d, '%s');",
problemSize, tol, min_ei, nBins, matlabLogFile));
lock = new Object();
nParameters = LB.length;
// TODO Configure me to run only id debug mode!
// listTestExecution();
}
public void addTestExecution(TestResult testResult)
throws MatlabInvocationException {
this.addTestExecution(testResult.getStates(),
testResult.getParameters());
}
private void addTestExecution(double[] stateSequence, double... parameters)
throws MatlabInvocationException {
synchronized (lock) {
try {
logger.debug("MatlabControlImpl.addTestExecution(): "
+ Arrays.toString(stateSequence));
// We must convert the arrays into matlab vars and then pass
// them by
// name -> MAKE THIS A COLUMN VECTOR
// double[][] _stateSequence = new double[1][];
// _stateSequence[0] = stateSequence;
// The vector MUST BE A COLUMN VECTOR !
double[][] _stateSequence = new double[stateSequence.length][1];
for (int i = 0; i < stateSequence.length; i++) {
_stateSequence[i][0] = stateSequence[i];
}
processor.setNumericArray("stateSequence",
new MatlabNumericArray(_stateSequence, null));
double[][] _parameters = new double[1][];
_parameters[0] = parameters;
processor.setNumericArray("parameters", new MatlabNumericArray(
_parameters, null));
// I have big problems in passing arrays as inputs using feval
// proxy.feval("update_training_data", "stateSequence",
// "parameters");
proxy.eval("update_training_data(stateSequence,parameters);");
} catch (MatlabInvocationException e) {
logger.error("", e);
throw e;
}
}
}
// This is mainly for testing purposes
Object listTestExecution() throws MatlabInvocationException {
try {
// Enable logging
proxy.showInConsoleHandler();
proxy.eval("global training_data");
Object result = proxy.getVariable("training_data");
logger.info("Training Data = " + result);
/*
* result is an object that maps the cell array in matlab:
* result.lenght=nxn result[i]=object[2] result[i][0]=string[#of
* cell elements names] for us is 2 parameters, phi
* result[i][1]=object[#of test executions]
* result[i][1][t]=object[#of cell elements values] for us is 2
* result[i][1][t][0]=double[#of paramters] -> values of parameters
* result[i][1][t][1]=double[1] -> phi
*/
return result;
} catch (MatlabInvocationException e) {
logger.error("", e);
throw e;
}
}
// TODO Is this really needed?
public void removeTestExecution(String ID) {
// TODO Auto-generated method stub
}
// TODO Registered test cases... maybe this is better to have it
// elsewhere... not in matlab
public List<double[]> getCriticalTestCases() {
// TODO Auto-generated method stub
return null;
}
/**
* Retrieve the (at most) n best parameters values according to the
* max(E[I]) formula. All the returned values have a max improvement that is
* greater or equals to minEI
*
* @throws MatlabInvocationException
*/
public List<double[]> getBestPlasticityTests(int n)
throws MatlabInvocationException {
synchronized (lock) {
/*
* IMPORTANT: Now that we use the discrete/integer problem we run
* into a non-termination issue, this results in the call possibly
* returning again the same tests. We need to deal with it: the
* heuristic is to take a random test 'far-away' from the repeated
* one and move on. If the same happens several time we are
* confident that the 'real' optimum is actually the one returned.
*/
logger.debug("MatlabControlImpl.getBestPlasticityTests() " + n);
proxy.setVariable("n", n);
Object[] result = proxy.returningEval(
"get_best_expected_improvements(n);", 1);
double[] _result = (double[]) (result[0]);
/*
* We know that each row will contains p+2+1 columns, the first two
* are the i,j values that identify the transition, the third is
* max_ei and the others p columns are the parameters that define
* the test case
*/
int nColums = nParameters + 2 + 1;
int nRows = _result.length / nColums;
List<double[]> theResult = new ArrayList<double[]>(nRows);
// Initialize the structure
for (int row = 0; row < nRows; row++) {
theResult.add(new double[nColums]);
}
for (int i = 0; i < _result.length; i++) {
theResult.get(i % nRows)[i / nRows] = _result[i];
}
return theResult;
}
}
public void exit() throws MatlabInvocationException {
if (proxy.isConnected()) {
proxy.exit();
}
}
// I, J, Phi
public List<double[]> inferModel(double[] stateSequence)
throws MatlabInvocationException {
logger.info("Infer Model from " + Arrays.toString(stateSequence));
List<double[]> result = new ArrayList<double[]>();
synchronized (lock) {
try {
// We must convert the arrays into matlab vars and then pass
// them by name;
// The vector MUST BE A COLUMN VECTOR !
double[][] _stateSequence = new double[stateSequence.length][1];
for (int i = 0; i < stateSequence.length; i++) {
_stateSequence[i][0] = stateSequence[i];
}
processor.setNumericArray("stateSequence",
new MatlabNumericArray(_stateSequence, null));
Object[] theResult = proxy.returningEval(
"infer_markov_model(stateSequence);", 1);
double[] _result = (double[]) (theResult[0]);
// We know that each row will contains 3 columns (i,j,phi_i,j)
int nColums = 3;
int nRows = _result.length / nColums;
// logger.debug("MatlabControlImpl.inferModel() Col = "
// + nColums);
// logger.debug("MatlabControlImpl.inferModel() Row = "
// + nRows);
result = new ArrayList<double[]>(nRows);
// Initialize the structure
for (int row = 0; row < nRows; row++) {
result.add(new double[nColums]);
}
for (int i = 0; i < _result.length; i++) {
result.get(i % nRows)[i / nRows] = _result[i];
}
if (result.size() > 0) {
logger.info("Inferred model as transition list: ");
for (double[] transition : result) {
logger.info(String.format("%f,%f -> %f", transition[0],
transition[1], transition[2]));
}
} else {
logger.info("No transitions were inferred !");
}
} catch (MatlabInvocationException e) {
logger.error("", e);
throw e;
}
}
return result;
}
}