/* An actor that evaluates matlab expressions with input ports
providing variables
Copyright (c) 1998-2014 The Regents of the University of California and
Research in Motion Limited.
All rights reserved.
Permission is hereby granted, without written agreement and without
license or royalty fees, to use, copy, modify, and distribute this
software and its documentation for any purpose, provided that the above
copyright notice and the following two paragraphs appear in all copies
of this software.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA OR RESEARCH IN MOTION
LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED
SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION
LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
ENHANCEMENTS, OR MODIFICATIONS.
PT_COPYRIGHT_VERSION_2
COPYRIGHTENDKEY
*/
package ptolemy.matlab;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import ptolemy.actor.Director;
import ptolemy.actor.IOPort;
import ptolemy.actor.TypedAtomicActor;
import ptolemy.actor.TypedIOPort;
import ptolemy.actor.gui.style.CheckBoxStyle;
import ptolemy.actor.gui.style.TextStyle;
import ptolemy.data.BooleanToken;
import ptolemy.data.DoubleToken;
import ptolemy.data.IntToken;
import ptolemy.data.StringToken;
import ptolemy.data.Token;
import ptolemy.data.expr.Parameter;
import ptolemy.data.expr.StringParameter;
import ptolemy.data.expr.UtilityFunctions;
import ptolemy.data.expr.Variable;
import ptolemy.data.type.BaseType;
import ptolemy.graph.Inequality;
import ptolemy.kernel.CompositeEntity;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.NameDuplicationException;
import ptolemy.kernel.util.Workspace;
import ptolemy.matlab.Engine.ConversionParameters;
///////////////////////////////////////////////////////////////////
//// Expression
/**
On each firing send an expression for evaluation to a matlab {@link
Engine}. The expression is any valid matlab expression, e.g.:
[out1, out2, ... ] = SomeMatlabFunctionOrExpression( in1, in2, ... );...
The expression may include references to the input port names, current
time (time), and a count of the firing (iteration). This
is similar to Expression.
To refer to parameters in scope, use $name or ${name} within
the expression.
The matlab engine is opened (started) during prefire() by the first
matlab Expression actor. Subsequent open()s simply increment a use
count.
At the start of fire(), clear variables;clear globals commands are
sent to matlab to clear its workspace. This helps detect errors where the
matlab expression refers to a matlab variable not initialized from the
input ports of this actor instance.
After the evaluation of the matlab expression is complete, the fire()
method iterates through names of output ports and converts matlab
variables with corresponding names to Tokens that are sent to the
corresponding output ports. Incorrect expressions are usually first
detected at this point by not finding the expected variables. If an
output port variable is not found in the matlab {@link Engine}, an
exception is thrown. The exception description string contains the last
stdout of the matlab engine that usually describes the error.
The {@link #get1x1asScalars} and {@link #getIntegerMatrices} control
data conversion (see {@link Engine} and
{@link Engine.ConversionParameters}).
A Parameter named packageDirectories may be added to this actor
to augment the search path of the matlab engine during the firing of this
actor. The value of this parameter should evaluate to a StringToken,
e.g.:
"path1, path2, ..."
containing a comma-separated list of paths to be prepended to the matlab
engine search path before expression is evaluated. The list may
contain paths relative to the directory in which ptolemy was started,
or any directory listed in the current classpath (in that order, first
match wins). See {@link ptolemy.data.expr.UtilityFunctions#findFile(String)}.
After evaluation, the previous search path is restored.
A Parameter named _debugging may be used to turn on debug print
statements to stdout from {@link Engine} and the ptmatlab JNI. An IntToken
with a value of 1 turns on Engine debug statements, a value of 2 adds
ptmatlab debug statements as well. A value of 0 or the absence of the
_debugging parameter yields normal operation.
For this actor to work, Matlab must be installed on your local
machine. In addition, your environment must be set properly. The
$PTII/bin/vergil
script does this for you, below are
instructions for users, such as Eclipse users, who are not using
$PTII/bin/vergil
.
Add the Matlab shared libraries to your the library path. In the examples
below $MATLAB
should be the location of your Matlab installation. For example, if
/usr/bin/matlab is a link:
bash-3.2$ which matlab
/usr/bin/matlab
bash-3.2$ ls -l /usr/bin/matlab
lrwxr-xr-x 1 root wheel 42 Jan 15 20:57 /usr/bin/matlab -> /Applications/MATLAB_R2009b.app/bin/matlab
bash-3.2$
Then $MATLAB would be /Applications/MATLAB_R2009b.app
- 32 Bit Mac (10.5?, Edit ~/.MacOSX/environment.plist)
export DYLD_LIBRARY_PATH=$MATLAB/bin/maci
- 64 Bit Mac (10.6?, Edit ~/.MacOSX/environment.plist)
export DYLD_LIBRARY_PATH=$MATLAB/bin/maci64
- 32 Bit Linux (Edit
export LD_LIBRARY_PATH=$MATLAB/bin/gnlx86
- 64 Bit Linux
export LD_LIBRARY_PATH=$MATLAB/bin/glnxa64
- Windows (Start|My Computer|Properties|Advanced
Environment Variables| Add the directory that contains
matlab.exe to your path)
Be sure that the matlab binary is in your path
@author Zoltan Kemenczy and Sean Simmons, Research in Motion Limited
@version $Id: Expression.java 70402 2014-10-23 00:52:20Z cxh $
@since Ptolemy II 2.0
@Pt.ProposedRating Yellow (zkemenczy)
@Pt.AcceptedRating Red (cxh)
*/
public class Expression extends TypedAtomicActor {
/** Construct an actor with the given container and name.
* @param container The container.
* @param name The name of this actor.
* @exception IllegalActionException If the actor cannot be contained
* by the proposed container.
* @exception NameDuplicationException If the container already has an
* actor with this name.
*/
public Expression(CompositeEntity container, String name)
throws NameDuplicationException, IllegalActionException {
super(container, name);
output = new TypedIOPort(this, "output", false, true);
expression = new StringParameter(this, "expression");
new TextStyle(expression, "Matlab expression");
_dataParameters = new Engine.ConversionParameters();
get1x1asScalars = new Parameter(this, "get1x1asScalars",
new BooleanToken(_dataParameters.getScalarMatrices));
new CheckBoxStyle(get1x1asScalars, "style");
getIntegerMatrices = new Parameter(this, "getIntegerMatrices",
new BooleanToken(_dataParameters.getIntMatrices));
new CheckBoxStyle(getIntegerMatrices, "style");
clearEnvironment = new Parameter(this, "clearEnvironment",
new BooleanToken(true));
clearEnvironment.setTypeEquals(BaseType.BOOLEAN);
clearEnvironment.setToken(BooleanToken.TRUE);
// _time is not needed, fire() sets a matlab variable directly
_iteration = new Variable(this, "_iteration", new IntToken(1));
}
///////////////////////////////////////////////////////////////////
//// ports and parameters ////
/** The output port. */
public TypedIOPort output;
/** The parameter that is evaluated to produce the output.
* Typically, this parameter evaluates an expression involving
* the inputs. To refer to parameters in scope within the expression,
* use $name or ${name}, where "name" is the name of the parameter.
*/
public StringParameter expression;
/** If true (checked), 1x1 matrix results are converted to
ScalarTokens instead of a 1x1 MatrixToken, default is
true. */
public Parameter get1x1asScalars;
/** If true, all double-valued matrix results are checked to see if
all elements represent integers, and if so, an IntMatrixToken is
returned, default is false for performance reasons. */
public Parameter getIntegerMatrices;
/** If true, clear variables and globals before each execution. */
public Parameter clearEnvironment;
///////////////////////////////////////////////////////////////////
//// public methods ////
/** Clone the actor into the specified workspace. This calls the
* base class and then sets the iteration
* public member to the parameters of the new actor.
* @param workspace The workspace for the new object.
* @return A new actor.
* @exception CloneNotSupportedException If a derived class contains
* an attribute that cannot be cloned.
*/
@Override
public Object clone(Workspace workspace) throws CloneNotSupportedException {
Expression newObject = (Expression) super.clone(workspace);
newObject._addPathCommand = null;
newObject._iteration = (Variable) newObject.getAttribute("_iteration");
newObject._iterationCount = 1;
newObject._previousPath = null;
newObject._inputTokens = new HashMap();
return newObject;
}
/** Open a matlab engine.
* @exception IllegalActionException If matlab engine not found.
*/
@Override
public void preinitialize() throws IllegalActionException {
super.preinitialize();
try {
matlabEngine = new Engine();
} catch (Throwable throwable) {
// LinkageError is and Error, not an exceptoin
throw new IllegalActionException(
this,
throwable,
"There was a problem invoking the Ptolemy II Matlab "
+ "interface.\nThe interface has been tested under "
+ "Linux, Mac OS X, and Windows.\n"
+ "The interface requires that Matlab be installed "
+ "on the local machine and that the ptmatlab "
+ "shared library available.\n"
+ "* Under Linux, you must have the LD_LIBRARY_PATH "
+ "environment variable set to include the directories "
+ "that contain libmx.so and libptmatlab.so.\n"
+ "* Under Mac OS X, you must have the DYLD_LIBRARY_PATH "
+ "environment variable set to include the directories "
+ "that contain libmx.dylib and libptmatlab.jnilib.\n"
+ "* Under Windows, you must have your PATH set to include "
+ "the Matlab bin/win32 or equivalent directory so that "
+ "libmex.dll is found and the directory that contains "
+ "libptmatlab.dll. "
+ "In addition, if you are running under Windows from "
+ "the Cygwin bash prompt, then you must start Vergil with "
+ "the -jni argument: $PTII/bin/vergil -jni. For details, "
+ "see $PTII/jni/package.html.\n"
+ "Refer to $PTII/ptolemy/matlab/makefile for more "
+ "information.");
}
// First set default debugging level, then check for more
matlabEngine.setDebugging((byte) 0);
Parameter debugging = (Parameter) getAttribute("_debugging");
if (debugging != null) {
Token t = debugging.getToken();
if (t instanceof IntToken) {
matlabEngine.setDebugging((byte) ((IntToken) t).intValue());
}
}
engine = matlabEngine.open();
}
/** Initialize the iteration count to 1.
* @exception IllegalActionException If the parent class throws it.
*/
@Override
public void initialize() throws IllegalActionException {
super.initialize();
_iterationCount = 1;
_iteration.setToken(new IntToken(_iterationCount));
// Process any additional directories to be added to matlab's
// path. The list may contain paths relative to the directory in
// which ptolemy was started or any directory listed in the current
// classpath (in this order, first match wins). See
// UtilityFunctions.findFile()
_addPathCommand = null; // Assume none
_previousPath = null;
Parameter packageDirectories = (Parameter) getAttribute("packageDirectories");
if (packageDirectories != null) {
StringTokenizer dirs = new StringTokenizer(
((StringToken) packageDirectories.getToken()).stringValue(),
",");
StringBuffer cellFormat = new StringBuffer(512);
cellFormat.append("{");
if (dirs.hasMoreTokens()) {
cellFormat.append("'"
+ UtilityFunctions.findFile(dirs.nextToken()) + "'");
}
while (dirs.hasMoreTokens()) {
cellFormat.append(",'"
+ UtilityFunctions.findFile(dirs.nextToken()) + "'");
}
cellFormat.append("}");
if (cellFormat.length() > 2) {
_addPathCommand = "addedPath_ = " + cellFormat.toString()
+ ";addpath(addedPath_{:});";
synchronized (Engine.semaphore) {
matlabEngine.evalString(engine, "previousPath_=path");
_previousPath = matlabEngine.get(engine, "previousPath_");
}
}
}
_dataParameters.getScalarMatrices = ((BooleanToken) get1x1asScalars
.getToken()).booleanValue();
_dataParameters.getIntMatrices = ((BooleanToken) getIntegerMatrices
.getToken()).booleanValue();
}
/** Return true if all input ports have at least one token.
* @return True if this actor is ready for firing, false otherwise.
* @exception IllegalActionException Not thrown in this base class.
*/
@Override
public boolean prefire() throws IllegalActionException {
Iterator inputPorts = inputPortList().iterator();
while (inputPorts.hasNext()) {
IOPort port = (IOPort) inputPorts.next();
if (!port.hasToken(0)) {
return false;
}
}
return super.prefire();
}
/** Evaluate the expression and send its result to the output.
* @exception IllegalActionException If the evaluation of the expression
* triggers it, or the evaluation yields a null result, or the evaluation
* yields an incompatible type, or if there is no director.
*/
@Override
public void fire() throws IllegalActionException {
super.fire();
Director director = getDirector();
if (director == null) {
throw new IllegalActionException(this, "No director!");
}
boolean clearEnvironmentValue = ((BooleanToken) clearEnvironment
.getToken()).booleanValue();
try {
// Read the input ports before acquiring the engine lock since
// get() may block depending on the director, e.g., PN.
for (TypedIOPort port : inputPortList()) {
_inputTokens.put(port.getName(), port.get(0));
}
synchronized (Engine.semaphore) {
if (clearEnvironmentValue) {
// The following clears variables, but preserves any
// persistent storage created by a function (this usually
// for speed-up purposes to avoid recalculation on every
// function call)
matlabEngine.evalString(engine,
"clear variables;clear globals");
}
if (_addPathCommand != null) {
matlabEngine.evalString(engine, _addPathCommand);
}
try {
matlabEngine.put(engine, "time", new DoubleToken(director
.getModelTime().getDoubleValue()));
} catch (IllegalActionException ex) {
throw new IllegalActionException(this, ex,
"Failed to set the \"time\" variable in the Matlab "
+ "engine to "
+ new DoubleToken(director.getModelTime()
.getDoubleValue()));
}
try {
matlabEngine
.put(engine, "iteration", _iteration.getToken());
} catch (IllegalActionException ex) {
throw new IllegalActionException(this, ex,
"Failed to set the \"iteration\" variable in the Matlab "
+ "engine to " + _iteration.getToken());
}
for (Map.Entry entry : _inputTokens.entrySet()) {
matlabEngine.put(engine, entry.getKey(), entry.getValue());
}
matlabEngine.evalString(engine, expression.stringValue());
Iterator outputPorts = outputPortList().iterator();
while (outputPorts.hasNext()) {
IOPort port = (IOPort) outputPorts.next();
// FIXME: Handle multiports
if (port.isOutsideConnected()) {
port.send(0, matlabEngine.get(engine, port.getName(),
_dataParameters));
}
}
// Restore previous path if path was modified above
if (_previousPath != null) {
matlabEngine.put(engine, "previousPath_", _previousPath);
matlabEngine.evalString(engine, "path(previousPath_);");
}
}
} finally {
// Remove references to any tokens that were read from input ports.
_inputTokens.clear();
}
}
/** Increment the iteration count.
* @exception IllegalActionException If the superclass throws it.
*/
@Override
public boolean postfire() throws IllegalActionException {
_iterationCount++;
_iteration.setToken(new IntToken(_iterationCount));
// This actor never requests termination.
return super.postfire();
}
/** Close matlab engine if it was open.
* @exception IllegalActionException Not thrown in this base class.
*/
@Override
public void wrapup() throws IllegalActionException {
super.wrapup();
if (matlabEngine != null) {
matlabEngine.close(engine);
}
engine = null;
}
///////////////////////////////////////////////////////////////////
//// protected methods ////
/** Default type constraints do not apply in this case, since the input
* type may be totally unrelated to the output type and cannot be
* inferred; return null.
* @return null
*/
@Override
protected Set _defaultTypeConstraints() {
return null;
}
private transient Engine matlabEngine = null;
long[] engine = null;
private Variable _iteration;
private int _iterationCount = 1;
private String _addPathCommand = null;
private Token _previousPath = null;
private transient ConversionParameters _dataParameters;
/** A map of input port names to tokens. */
private Map _inputTokens = new HashMap();
}