/** Representation of an interface that relates its inputs and outputs. * */ package ptolemy.apps.interfaces; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import ptolemy.actor.IOPort; import ptolemy.kernel.Relation; /** Representation of an interface that relates its inputs and outputs, * in the style of the EMSOFT 2009 paper "On Relational Interfaces." * * @author Ben Lickly * */ public class RelationalInterface { /** Construct a new interface given lists of ports and a contract. * * This is a convenience method that simply extracts the names of * ports and uses then uses the other constructor strategy. * * @param inputPortList The input ports. * @param outputPortList The output ports. * @param contract The contract. */ public RelationalInterface(final List<IOPort> inputPortList, final List<IOPort> outputPortList, final String contract) { final Set<String> inputs = new HashSet<String>(); Iterator<IOPort> ports = inputPortList.iterator(); while (ports.hasNext()) { inputs.add(ports.next().getName()); } final Set<String> outputs = new HashSet<String>(); ports = outputPortList.iterator(); while (ports.hasNext()) { outputs.add(ports.next().getName()); } _init(inputs, outputs, contract); } /** Construct a new interface given a set of input variables, a set of * output variables, and a contract. * * @param inputs The input variables. * @param outputs The output variables. * @param contract The contract. */ public RelationalInterface(final Set<String> inputs, final Set<String> outputs, final String contract) { _init(inputs, outputs, contract); } /////////////////////////////////////////////////////////////////// //// public methods //// /** Add a the given feedback connection to this interface. * * FIXME: This the only type of composition that modifies * the original interface. We should change this. * * @param connection The feedback connection. */ public void addFeedback(final Connection connection) { if (_mooreInputs().contains(connection._inputPort)) { _inputPorts.remove(connection._inputPort); _outputPorts.remove(connection._outputPort); _contract = "(and " + _contract + " (== " + connection._inputPort + " " + connection._outputPort + "))"; } else { assert false; //FIXME: Throw exception } } /** Return an interface that results from the cascade composition of * this interface and the given interface. * * Note that this is not commutative. The outputs of this * interface must be connected to the inputs of the given interface. * * @param rhs The interface to compose with. * @param connections The connections from this to rhs. * @return The comosition's interface. */ public RelationalInterface cascadeComposeWith( final RelationalInterface rhs, final Set<Connection> connections) { final Set<String> newInputs = new HashSet<String>(); newInputs.addAll(_inputPorts); newInputs.addAll(rhs._inputPorts); for (final Connection c : connections) { newInputs.remove(c._inputPort); } final Set<String> newOutputs = new HashSet<String>(); newOutputs.addAll(_outputPorts); newOutputs.addAll(rhs._outputPorts); for (final Connection c : connections) { newOutputs.add(c._inputPort); } // Build up the contract for cascade composition. // This is a translation of Definition 10 (composition by connection) // in the EMSOFT 2009 paper "On Relational Interfaces" final String connectionContracts = Connection.getContract(connections); final StringBuffer y = new StringBuffer('('); for (final Connection c : connections) { y.append(c._inputPort + "::int " + c._outputPort + "::int "); } for (final String firstInterfaceOutputVariable : _outputPorts) { y.append(firstInterfaceOutputVariable + "::int "); } y.append(')'); // Y = "(" // for (port : connections U outputs) { // Y += port.name + "::int " // } // Y += ")" final String phi = "(=> (and " + _contract + " " + connectionContracts + ") " + rhs.inContract() + ")"; final String newContract = "(and " + _contract + " " + rhs._contract + " " + connectionContracts + " (forall " + y.toString() + " " + phi + "))"; return new RelationalInterface(newInputs, newOutputs, newContract); } /** Return a string representation of the interface contract. * * @return The contract. */ public String getContract() { return _contract; } /** Return a string that Yices can check for satisfiability. * * @return A string in the yices input expression language. */ public String getYicesInput() { final StringBuffer yicesInput = new StringBuffer(); for (final String inputPort : _inputPorts) { yicesInput.append("(define " + inputPort + "::int)\n"); } for (final String outputPort : _outputPorts) { yicesInput.append("(define " + outputPort + "::int)\n"); } yicesInput.append("(assert " + _contract + ")\n"); return yicesInput.toString(); } /** Return the contract of the input assumption of this interface. * * @return The input assumption. */ private String inContract() { final StringBuffer result = new StringBuffer("(exists ("); for (final String output : _outputPorts) { result.append(output + "::int "); } result.append(") " + _contract + ")"); return result.toString(); } /** Return an interface that results from the parallel composition of * this interface and the given interface. * * @param rhs The interface to compose with. * @return The comosition's interface. */ public RelationalInterface parallelComposeWith(final RelationalInterface rhs) { final Set<String> newInputs = new HashSet<String>(); newInputs.addAll(_inputPorts); newInputs.addAll(rhs._inputPorts); final Set<String> newOutputs = new HashSet<String>(); newOutputs.addAll(_outputPorts); newOutputs.addAll(rhs._outputPorts); final String newContract = "(and " + _contract + " " + rhs._contract + ")"; return new RelationalInterface(newInputs, newOutputs, newContract); } /////////////////////////////////////////////////////////////////// //// private methods //// /** Construct a RelationalInterface with the given inputs port names, * output port names, and contract. * * @param inputs A set of the names of input variables. * @param outputs A set of the names of output variables. * @param contract A Yices-compatible string of the contract. */ private void _init(final Set<String> inputs, final Set<String> outputs, final String contract) { // Inputs and outputs must be disjoint. for (final String inputPort : inputs) { assert !outputs.contains(inputPort); } _inputPorts = inputs; _outputPorts = outputs; _contract = contract; } /** Return the subset of the input variables that are Moore. * These are just the input variables that do not appear in the contract. * * @return The Moore inputs. */ private Set<String> _mooreInputs() { final Set<String> mooreInputs = new HashSet<String>(); for (final String port : _inputPorts) { // want to check for "\bport\b" regex, but this is simpler if (!_contract.contains(" " + port + " ") && !_contract.contains(" " + port + ")")) { mooreInputs.add(port); } } return mooreInputs; } /////////////////////////////////////////////////////////////////// //// private variables //// /** The set of input variables. */ private Set<String> _inputPorts = new HashSet<String>(); /** The set of output variables. */ private Set<String> _outputPorts = new HashSet<String>(); /** The contract of the interface. */ private String _contract; } /** A class that represents a connection between two ports. * * @author Ben Lickly */ class Connection { /** Construct a connection from the given output port * to the given input port. * @param outputPort The name of the output port. * @param inputPort The name of the input port. */ Connection(final String outputPort, final String inputPort) { _outputPort = outputPort; _inputPort = inputPort; } /** Return the set of connections represented by a relation. * * This is a set because relations can connect multiple ports, * but a connection is between two ports only. * * @param r The relation to convert. * @return The set of connections that r represents. */ public static Set<Connection> connectionsFromRelations(final Relation r) { final Set<Connection> connections = new HashSet<Connection>(); IOPort outputPort = null; for (final Object o : r.linkedPortList()) { if (!(o instanceof IOPort)) { continue; } final IOPort p = (IOPort) o; if (p.isOutput()) { assert outputPort == null; // FIXME: Change to exception outputPort = p; } } assert outputPort != null; //FIXME: Change to exception for (final Object o : r.linkedPortList(outputPort)) { if (!(o instanceof IOPort)) { continue; } final IOPort p = (IOPort) o; connections.add(new Connection(outputPort.getName(), p.getName())); } return connections; } /** Return the contract specifying the equality caused by a set of * connections. * * @param connections The set of connections. * @return The contract. */ public static String getContract(final Set<Connection> connections) { final StringBuffer contract = new StringBuffer("(and "); for (final Connection c : connections) { contract.append("(== " + c._inputPort + " " + c._outputPort + ")"); } contract.append(")"); return contract.toString(); } /** The start of the connection. */ public String _outputPort; /** The end of the connection. */ public String _inputPort; }