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}