001/* Causality interface for FSM actors.
002
003 Copyright (c) 2003-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.domains.modal.kernel;
029
030import java.util.Collection;
031import java.util.HashMap;
032import java.util.HashSet;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036
037import ptolemy.actor.Actor;
038import ptolemy.actor.IOPort;
039import ptolemy.actor.TypedActor;
040import ptolemy.actor.parameters.ParameterPort;
041import ptolemy.actor.util.CausalityInterface;
042import ptolemy.actor.util.CausalityInterfaceForComposites;
043import ptolemy.actor.util.Dependency;
044import ptolemy.data.BooleanToken;
045import ptolemy.data.expr.ASTPtRootNode;
046import ptolemy.data.expr.ParseTreeFreeVariableCollector;
047import ptolemy.data.expr.PtParser;
048import ptolemy.kernel.util.IllegalActionException;
049import ptolemy.kernel.util.NamedObj;
050
051///////////////////////////////////////////////////////////////////
052////FSMCausalityInterface
053
054/**
055This class infers the causality interface of an FSMActor by checking
056the guards and actions of the transitions. If any transition in the
057model has an output action that writes to a port and a guard that
058references an input port, then there is a direct dependency of
059that output on that input. Otherwise, there is no dependency.
060Note that this is a conservative analysis in that it may indicate
061a dependency when there is none. For example, if all outgoing
062transitions from a state produce the same output value, and
063a transition is always taken, then irrespective of the guards,
064the output has no dependency on the inputs.  A precise analysis,
065however, is much more difficult (probably undecidable).
066<p>
067All input ports that affect the state (i.e. that are mentioned in
068any guard) must be in an equivalence class. Otherwise, we cannot
069reliably make a decision about what the next state is. In addition,
070if any input in a refinement affects an output, that input must
071also be in this equivalence class. Otherwise, the scheduler
072will assume there is no relationship between these inputs and
073could provide an event that triggers a state transition in an
074earlier firing than an event that triggers an output from the
075current refinement.
076
077@author Edward A. Lee
078@version $Id$
079@since Ptolemy II 8.0
080@Pt.ProposedRating Yellow (eal)
081@Pt.AcceptedRating Red (eal)
082 */
083public class FSMCausalityInterface extends CausalityInterfaceForComposites {
084
085    /** Construct a causality interface for the specified actor.
086     *  @param actor The actor for which this is a causality interface.
087     *   This is required to be an instance of CompositeEntity.
088     *  @param defaultDependency The default dependency of an output
089     *   port on an input port.
090     *  @exception IllegalArgumentException If the actor parameter is not
091     *  an instance of CompositeEntity.
092     */
093    public FSMCausalityInterface(Actor actor, Dependency defaultDependency)
094            throws IllegalArgumentException {
095        super(actor, defaultDependency);
096        if (!(actor instanceof FSMActor)) {
097            throw new IllegalArgumentException("Cannot create an instance of "
098                    + "FSMCausalityInterface for " + actor.getFullName()
099                    + ", which is not an FSMActor.");
100        }
101    }
102
103    ///////////////////////////////////////////////////////////////////
104    ////                         public methods                    ////
105
106    /** Return the dependency between the specified input port
107     *  and the specified output port.  This is done by checking
108     *  the guards and actions of all the transitions.
109     *  When called for the first time since a change in the model
110     *  structure, this method performs the complete analysis of
111     *  the FSM and caches the result. Subsequent calls just
112     *  look up the result.
113     *  @param input The input port.
114     *  @param output The output port, or null to update the
115     *   dependencies (and record equivalence classes) without
116     *   requiring there to be an output port.
117     *  @return The dependency between the specified input port
118     *   and the specified output port, or null if a null output
119     *   is port specified.
120     *  @exception IllegalActionException If a guard expression cannot be parsed.
121     */
122    @Override
123    public Dependency getDependency(IOPort input, IOPort output)
124            throws IllegalActionException {
125        // Cast is safe because this is checked in the constructor
126        FSMActor actor = (FSMActor) _actor;
127
128        // If the dependency is not up-to-date, then update it.
129        long workspaceVersion = actor.workspace().getVersion();
130        if (_dependencyVersion != workspaceVersion) {
131            // Need to update dependencies. The cached version
132            // is obsolete.
133            boolean stateDependentCausality = ((BooleanToken) actor.stateDependentCausality
134                    .getToken()).booleanValue();
135            try {
136                actor.workspace().getReadAccess();
137                _reverseDependencies = new HashMap<IOPort, Map<IOPort, Dependency>>();
138                _forwardDependencies = new HashMap<IOPort, Map<IOPort, Dependency>>();
139
140                // Initialize the equivalence classes to contain each input port.
141                _equivalenceClasses = new HashMap<IOPort, Collection<IOPort>>();
142                List<IOPort> actorInputs = _actor.inputPortList();
143                for (IOPort actorInput : actorInputs) {
144                    Set<IOPort> equivalences = new HashSet<IOPort>();
145                    equivalences.add(actorInput);
146                    _equivalenceClasses.put(actorInput, equivalences);
147                }
148
149                // Keep track of all the ports that must go into the
150                // equivalence class of input ports that affect the state.
151                Collection<IOPort> stateEquivalentPorts = new HashSet<IOPort>();
152
153                // Iterate over all the transitions or just the transitions
154                // of the current state.
155                Collection<Transition> transitions;
156                if (!stateDependentCausality) {
157                    transitions = actor.relationList();
158                } else {
159                    State currentState = actor.currentState();
160                    transitions = currentState.outgoingPort
161                            .linkedRelationList();
162                }
163                for (Transition transition : transitions) {
164                    // Collect all the output ports that are written to on this transition.
165                    Set<IOPort> outputs = new HashSet<IOPort>();
166
167                    // Look only at the "choice" actions because "commit" actions
168                    // do not execute until postfire(), and hence do not imply
169                    // an input/output dependency.
170                    List<AbstractActionsAttribute> actions = transition
171                            .choiceActionList();
172                    for (AbstractActionsAttribute action : actions) {
173                        List<String> names = action.getDestinationNameList();
174                        for (String name : names) {
175                            NamedObj destination = action.getDestination(name);
176                            if (destination instanceof IOPort
177                                    && ((IOPort) destination).isOutput()) {
178                                // Found an output that is written to.
179                                outputs.add((IOPort) destination);
180                            }
181                        }
182                    }
183
184                    // Now handle the guard expression, finding
185                    // all referenced input ports.
186                    Set<IOPort> inputs = new HashSet<IOPort>();
187                    String guard = transition.getGuardExpression();
188                    // The guard expression may be empty (in the Ptera domain,
189                    // for example). Continue if this is the case.
190                    if (guard.trim().equals("")) {
191                        continue;
192                    }
193                    // Parse the guard expression.
194                    PtParser parser = new PtParser();
195                    try {
196                        ASTPtRootNode guardParseTree = parser
197                                .generateParseTree(guard);
198                        ParseTreeFreeVariableCollector collector = new ParseTreeFreeVariableCollector();
199                        Set<String> freeVariables = collector
200                                .collectFreeVariables(guardParseTree);
201                        for (String freeVariable : freeVariables) {
202                            // Reach into the FSMActor to get the port.
203                            IOPort port = actor
204                                    ._getPortForIdentifier(freeVariable);
205                            if (port != null && port.isInput()) {
206                                // Found a reference to an input port in the guard.
207                                inputs.add(port);
208                            }
209                        }
210                    } catch (IllegalActionException ex) {
211                        throw new IllegalActionException(actor, ex,
212                                "Failed to parse guard expression \"" + guard
213                                        + "\"");
214                    }
215                    if (inputs.isEmpty()) {
216                        continue;
217                    }
218                    stateEquivalentPorts.addAll(inputs);
219
220                    // Set dependencies of all the found output
221                    // ports on all the found input ports.
222                    for (IOPort writtenOutput : outputs) {
223                        Map<IOPort, Dependency> outputMap = _reverseDependencies
224                                .get(writtenOutput);
225                        if (outputMap == null) {
226                            outputMap = new HashMap<IOPort, Dependency>();
227                            _reverseDependencies.put(writtenOutput, outputMap);
228                        }
229                        for (IOPort readInput : inputs) {
230                            outputMap.put(readInput,
231                                    _defaultDependency.oTimesIdentity());
232
233                            // Now handle the forward dependencies.
234                            Map<IOPort, Dependency> inputMap = _forwardDependencies
235                                    .get(readInput);
236                            if (inputMap == null) {
237                                inputMap = new HashMap<IOPort, Dependency>();
238                                _forwardDependencies.put(readInput, inputMap);
239                            }
240                            inputMap.put(writtenOutput,
241                                    _defaultDependency.oTimesIdentity());
242                        }
243                    }
244                } // End of iteration over transitions.
245
246                // Iterate over the refinements to find any additional ports that may
247                // have to be added to the state equivalent ports. These are input
248                // ports where the corresponding input port on the refinement has
249                // a direct effect on an output.
250                Collection<State> states;
251                if (!stateDependentCausality) {
252                    states = actor.entityList();
253                } else {
254                    State currentState = actor.currentState();
255                    states = new HashSet<State>();
256                    states.add(currentState);
257                }
258                for (State state : states) {
259                    TypedActor[] refinements = state.getRefinement();
260                    if (refinements != null && refinements.length > 0) {
261                        for (TypedActor refinement : refinements) {
262                            // The following causes test failures in models such as
263                            // ptolemy/domains/modal/test/auto/ABPTest.xml
264                            // The error is caught in the FSM Director
265                            //                            if (!((CompositeActor)refinement).isOpaque()) {
266                            //                                throw new IllegalActionException(refinement, "Refinement is missing a director!");
267                            //                            }
268                            CausalityInterface causality = refinement
269                                    .getCausalityInterface();
270                            // For each output port, find the input ports that affect it.
271                            Collection<IOPort> outputs = refinement
272                                    .outputPortList();
273                            for (IOPort refinementOutput : outputs) {
274                                Collection<IOPort> inputs = causality
275                                        .dependentPorts(refinementOutput);
276                                for (IOPort refinementInput : inputs) {
277                                    Collection<IOPort> equivalents = causality
278                                            .equivalentPorts(refinementInput);
279                                    for (IOPort equivalent : equivalents) {
280                                        // Whew... Need to add the port with the same name to
281                                        // the state equivalents.
282                                        IOPort port = (IOPort) actor
283                                                .getPort(equivalent.getName());
284                                        // This should not be null, but if it is, ignore.
285                                        if (port != null) {
286                                            stateEquivalentPorts.add(port);
287                                        }
288                                    }
289                                }
290                            }
291                        }
292                    }
293                }
294                // If any input is an instance of ParameterPort, then
295                // all inputs must be in the same equivalence class.
296                List<IOPort> inputs = _actor.inputPortList();
297                for (IOPort actorInput : inputs) {
298                    if (actorInput instanceof ParameterPort) {
299                        stateEquivalentPorts = inputs;
300                        break;
301                    }
302                }
303                // Now set the equivalence classes.
304                for (IOPort equivalent : stateEquivalentPorts) {
305                    _equivalenceClasses.put(equivalent, stateEquivalentPorts);
306                }
307            } finally {
308                actor.workspace().doneReading();
309            }
310            _dependencyVersion = workspaceVersion;
311        }
312        if (output == null) {
313            return null;
314        }
315        Map<IOPort, Dependency> inputMap = _forwardDependencies.get(input);
316        if (inputMap != null) {
317            Dependency result = inputMap.get(output);
318            if (result != null) {
319                return result;
320            }
321        }
322        // If there is no recorded dependency, then reply
323        // with the additive identity (which indicates no
324        // dependency).
325        return _defaultDependency.oPlusIdentity();
326    }
327}