001/* An analysis that finds the constant variables in a ptolemy model
002
003 Copyright (c) 2003-2013 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 */
027package ptolemy.actor.util;
028
029import java.util.Collections;
030import java.util.HashMap;
031import java.util.HashSet;
032import java.util.Iterator;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037
038import ptolemy.actor.CompositeActor;
039import ptolemy.actor.Manager;
040import ptolemy.actor.parameters.ParameterPort;
041import ptolemy.actor.parameters.PortParameter;
042import ptolemy.data.Token;
043import ptolemy.data.expr.ModelScope;
044import ptolemy.data.expr.Variable;
045import ptolemy.graph.DirectedGraph;
046import ptolemy.graph.Edge;
047import ptolemy.graph.Node;
048import ptolemy.kernel.CompositeEntity;
049import ptolemy.kernel.Entity;
050import ptolemy.kernel.Port;
051import ptolemy.kernel.util.Attribute;
052import ptolemy.kernel.util.IllegalActionException;
053import ptolemy.kernel.util.NamedObj;
054
055///////////////////////////////////////////////////////////////////
056//// ConstVariableModelAnalysis
057
058/**
059 An analysis that traverses a model to determine all the constant
060 variables in a hierarchical model.  Basically, a constant variable in
061 a particular model is any variable in the model that is defined by a
062 expression of constants, or any variable that is defined by an
063 expression of constants and identifiers that reference other constant
064 variables.
065
066 <p> This class computes the set of constant variables by computing the
067 set of variables that are not constant and then performing the
068 complement.  This is somewhat easier to compute.  The computation is
069 performed in two passes, the first of which extracts the set of
070 variables which must be not-constant either by not being evaluatable,
071 by inclusion in an initial set, by virtue of being a PortParameter
072 with an external connection, or by assignment from within a modal
073 model.  The second pass collects all the variables which are not
074 constant because they depend on other variables which are not
075 constant.  This class also recognizes dependence declarations
076 represented by the {@link DependencyDeclaration} class.
077
078 <p> This class also determines the "least change context" of each
079 dynamic variable.  The least change context of a variable is
080 typically an actor that contains that variable.  During a firing of
081 the least change context, the variable's value is guaranteed to not
082 change.  This analysis is important for supporting parameter changes
083 in the context of domains that perform scheduling based on parameter
084 values, like SDF and PSDF.  The least change context of a
085 PortParameter with an external connection must be a container of the
086 PortParameter.  The least change context of a variable assigned by a
087 finite state machine in a modal model must be a container of the
088 finite state machine.  The change context of asserted not constant
089 variables and variables with no expression are assumed to be the
090 toplevel of the model.  Note that in some cases (typically when a
091 variable is modified from multiple sources which are not
092 hierarchically related), no least change context may exist.
093
094 @author Stephen Neuendorffer
095 @version $Id$
096 @since Ptolemy II 4.0
097 @Pt.ProposedRating Yellow (neuendor)
098 @Pt.AcceptedRating Yellow (neuendor)
099 */
100public class ConstVariableModelAnalysis {
101    /** Create a dummy analysis for actors that are not contained
102     *  in a model.
103     */
104    public ConstVariableModelAnalysis() {
105        _variableToChangeContext = new HashMap();
106    }
107
108    /** Analyze the given model to determine which variables must be
109     *  constants and which variables may change dynamically during
110     *  execution.  In addition, store the intermediate results for
111     *  contained actors so they can be retrieved by the
112     *  getConstVariables() method.
113     *  @param model The model to be analyzed.
114     *  @exception IllegalActionException If an exception occurs
115     *  during analysis.
116     */
117    public ConstVariableModelAnalysis(Entity model)
118            throws IllegalActionException {
119        this(model, Collections.EMPTY_SET);
120    }
121
122    /** Analyze the given model to determine which variables must be
123     *  constants and which variables may change dynamically during
124     *  execution, given that all variables in the given set may
125     *  change dynamically.  In addition, store the intermediate
126     *  results for contained actors so they can be retrieved by the
127     *  getConstVariables() method.
128     *  @param model The model to be analyzed.
129     *  @param variableSet The set to be analyzed.
130     *  @exception IllegalActionException If an exception occurs
131     *  during analysis.
132     */
133    public ConstVariableModelAnalysis(Entity model, Set variableSet)
134            throws IllegalActionException {
135        _variableToChangeContext = new HashMap();
136
137        for (Iterator variables = variableSet.iterator(); variables
138                .hasNext();) {
139            Variable variable = (Variable) variables.next();
140            _variableToChangeContext.put(variable, model);
141        }
142
143        _dependencyGraph = new DirectedGraph();
144
145        _collectConstraints(model);
146        _analyzeAllVariables();
147    }
148
149    ///////////////////////////////////////////////////////////////////
150    ////                         public methods                    ////
151
152    /** Add the information in the given dependency declaration to the
153     *  dependence graph of this analysis.  This method can be called
154     *  by users of this class to update the analysis without
155     *  recomputing all of the information from scratch.
156     *  @param declaration The given dependency declaration.
157     */
158    public void addDependencyDeclaration(DependencyDeclaration declaration) {
159        _addDependencyDeclaration(declaration);
160        _analyzeAllVariables();
161    }
162
163    /** Return the analysis that is active for the given object.
164     *  @param object The given object.
165     *  @return The active analysis for the given object.
166     *  @exception IllegalActionException If an exception occurs during
167     *  analysis.
168     */
169    public static ConstVariableModelAnalysis getAnalysis(NamedObj object)
170            throws IllegalActionException {
171        if (object.toplevel() instanceof CompositeActor) {
172            CompositeActor toplevel = (CompositeActor) object.toplevel();
173            Manager manager = toplevel.getManager();
174
175            if (manager == null) {
176                throw new IllegalActionException(object, "No Manager found?");
177            }
178            ConstVariableModelAnalysis analysis = (ConstVariableModelAnalysis) manager
179                    .getAnalysis("ConstVariableModelAnalysis");
180
181            if (analysis == null) {
182                analysis = new ConstVariableModelAnalysis(toplevel);
183                manager.addAnalysis("ConstVariableModelAnalysis", analysis);
184            }
185
186            return analysis;
187        } else {
188            return new ConstVariableModelAnalysis();
189        }
190    }
191
192    /** Return the change context of the given variable.  This an
193     *  actor containing the variable, such that the variable is
194     *  guaranteed not to change values during a firing of the actor.
195     *  If the variable is constant, or no change context exists, then
196     *  return null.
197     *  @param variable The given variable.
198     *  @return The change context of the given variable.
199     */
200    public Entity getChangeContext(Variable variable) {
201        return (Entity) _variableToChangeContext.get(variable);
202    }
203
204    /** Return the constant value of the given parameter, if the
205     *  parameter is actually constant.
206     *  @param variable The given variable.
207     *  @return The constant value of the given variable.
208     *  @exception IllegalActionException If the given parameter is
209     *  not a constant parameter, as determined by this analysis.
210     */
211    public Token getConstantValue(Variable variable)
212            throws IllegalActionException {
213        if (!isConstant(variable)) {
214            throw new IllegalActionException(variable,
215                    "This variable does not have a constant value.");
216        }
217
218        return variable.getToken();
219    }
220
221    /** Return the computed constant variables for the given container.
222     *  @param container The given container.
223     *  @return The computed constant variables.
224     *  @exception RuntimeException If the constant variables for the
225     *  container have not already been computed.
226     */
227    public Set getConstVariables(NamedObj container) {
228        List variables = container.attributeList(Variable.class);
229        variables.removeAll(_variableToChangeContext.keySet());
230        return new HashSet(variables);
231    }
232
233    /** Return the parameter dependency graph constructed through this
234     *  analysis.
235     *  @return The parameter dependency graph.
236     */
237    public DirectedGraph getDependencyGraph() {
238        return _dependencyGraph;
239    }
240
241    /** Return the computed not constant variables for the given container.
242     *  @param container The given container.
243     *  @return The computed not constant variables.
244     *  @exception RuntimeException If the constant variables for the
245     *  container have not already been computed.
246     */
247    public Set getNotConstVariables(NamedObj container) {
248        List variables = container.attributeList(Variable.class);
249        variables.removeAll(getConstVariables(container));
250        return new HashSet(variables);
251    }
252
253    /** Return the set of variables anywhere in the model that have
254     *  the given container as least change context.
255     *  @param container The given container.
256     *  @return The set of variables anywhere in the model that have
257     *  the given container as least change context.
258     */
259    public Set getVariablesWithChangeContext(NamedObj container) {
260        Set variableSet = new HashSet();
261
262        for (Iterator i = _variableToChangeContext.keySet().iterator(); i
263                .hasNext();) {
264            Object key = i.next();
265            Object value = _variableToChangeContext.get(key);
266
267            if (value == container) {
268                variableSet.add(key);
269            }
270        }
271
272        return variableSet;
273    }
274
275    /** Return true if the given variable is not reconfigured in the
276     *  model.  The variable is assumed to be contained by the model
277     *  this analysis was created with.
278     *  @param variable The given variable.
279     *  @return True If the given variable is not reconfigured in the model.
280     */
281    public boolean isConstant(Variable variable) {
282        return !_variableToChangeContext.keySet().contains(variable);
283    }
284
285    /** Return true if the variable has been analyzed by this analysis
286     *  and it depends on no other parameters.
287     *  @param variable The given variable.
288     *  @return True If the variable has been analyzed by this analysis
289     *  and it depends on no other parameters
290     */
291    public boolean isIndependent(Variable variable) {
292        if (_dependencyGraph
293                .backwardReachableNodes(_dependencyGraph.node(variable))
294                .size() > 0) {
295            return false;
296        } else {
297            return true;
298        }
299    }
300
301    ///////////////////////////////////////////////////////////////////
302    ////                         private methods                   ////
303    // Add the dependence information from the given attribute to the
304    // dependence graph.
305    private void _addDependencyDeclaration(DependencyDeclaration declaration) {
306        Variable variable = (Variable) declaration.getContainer();
307        Node targetNode = _getNode(variable);
308
309        for (Iterator dependents = declaration.getDependents()
310                .iterator(); dependents.hasNext();) {
311            Variable dependent = (Variable) dependents.next();
312            Node dependentNode = _getNode(dependent);
313
314            //  if (!_dependencyGraph.edgeExists(node, targetNode)) {
315            _dependencyGraph.addEdge(dependentNode, targetNode);
316
317            //}
318        }
319    }
320
321    // Collect all of the constraints from the given variable.
322    private void _collectVariableConstraints(Variable variable) {
323        Node targetNode = _getNode(variable);
324
325        // compute the variables.
326        try {
327            Set freeIdentifiers = variable.getFreeIdentifiers();
328
329            for (Iterator names = freeIdentifiers.iterator(); names
330                    .hasNext();) {
331                String name = (String) names.next();
332                Variable dependent = ModelScope.getScopedVariable(variable,
333                        variable, name);
334
335                if (dependent != null) {
336                    Node dependentNode = _getNode(dependent);
337                    _dependencyGraph.addEdge(dependentNode, targetNode);
338                }
339            }
340        } catch (IllegalActionException ex) {
341            // Assume that this will be changed later...
342            // i.e. input_isPresent in FSM.
343            // Note that this also traps expressions that
344            // have no value as being variable...
345            _updateChangeContext(variable, (Entity) variable.toplevel());
346        }
347    }
348
349    // Collect the set of variables in the given entity which might
350    // change during execution.  This method adds an entry in the
351    // given map from each dynamic parameter deeply contained in the
352    // given entity to the change context of that parameter.
353    private void _collectConstraints(NamedObj container)
354            throws IllegalActionException {
355        if (container instanceof Variable) {
356            Variable variable = (Variable) container;
357            _collectVariableConstraints(variable);
358        }
359
360        if (container instanceof DependencyDeclaration) {
361            DependencyDeclaration declaration = (DependencyDeclaration) container;
362            _addDependencyDeclaration(declaration);
363        }
364
365        if (container instanceof PortParameter) {
366            PortParameter parameter = (PortParameter) container;
367            ParameterPort port = parameter.getPort();
368
369            // Under what conditions is a PortParameter not associated
370            // with a port?
371            if (port != null && port.isOutsideConnected()) {
372                _updateChangeContext(parameter,
373                        (Entity) parameter.getContainer());
374            }
375        }
376
377        if (container instanceof ExplicitChangeContext) {
378            List list = ((ExplicitChangeContext) container)
379                    .getModifiedVariables();
380
381            for (Iterator variables = list.iterator(); variables.hasNext();) {
382                Variable variable = (Variable) variables.next();
383                _updateChangeContext(variable,
384                        ((ExplicitChangeContext) container).getContext());
385            }
386        }
387
388        // Recurse through the whole model.
389        for (Iterator attributes = container.attributeList()
390                .iterator(); attributes.hasNext();) {
391            Attribute attribute = (Attribute) attributes.next();
392            _collectConstraints(attribute);
393        }
394
395        if (container instanceof CompositeEntity) {
396            CompositeEntity composite = (CompositeEntity) container;
397
398            for (Iterator entities = composite.entityList().iterator(); entities
399                    .hasNext();) {
400                _collectConstraints((Entity) entities.next());
401            }
402        }
403
404        if (container instanceof Entity) {
405            for (Iterator ports = ((Entity) container).portList()
406                    .iterator(); ports.hasNext();) {
407                Port port = (Port) ports.next();
408                _collectConstraints(port);
409            }
410        }
411    }
412
413    // Recursively compute the set of constant variables for all actors
414    // deeply contained in the given model.
415    private void _analyzeAllVariables() {
416        // Sets of variables used to track the fixed point iteration.
417        LinkedList workList = new LinkedList(_variableToChangeContext.keySet());
418
419        while (!workList.isEmpty()) {
420            Variable variable = (Variable) workList.removeFirst();
421            Node node = _dependencyGraph.node(variable);
422            Entity changeContext = (Entity) _variableToChangeContext
423                    .get(variable);
424
425            for (Iterator outputEdges = _dependencyGraph.outputEdges(node)
426                    .iterator(); outputEdges.hasNext();) {
427                Node sinkNode = ((Edge) outputEdges.next()).sink();
428                Variable targetVariable = (Variable) sinkNode.getWeight();
429
430                if (_updateChangeContext(targetVariable, changeContext)
431                        && !workList.contains(targetVariable)) {
432                    workList.addLast(targetVariable);
433                }
434            }
435        }
436    }
437
438    // Get the node for the given variable, adding it to the graph if
439    // necessary.
440    private Node _getNode(Variable variable) {
441        if (_dependencyGraph.containsNodeWeight(variable)) {
442            return _dependencyGraph.node(variable);
443        } else {
444            return _dependencyGraph.addNodeWeight(variable);
445        }
446    }
447
448    // Update the change context associated with the given variable to
449    // be at least the given change context.
450    // return true if a change occurred
451    private final boolean _updateChangeContext(Variable variable,
452            Entity changeContext) {
453        if (_variableToChangeContext.keySet().contains(variable)) {
454            Entity oldChangeContext = (Entity) _variableToChangeContext
455                    .get(variable);
456
457            // System.out.println("variable = " + variable);
458            // System.out.println("oldChangeContext = " + oldChangeContext);
459            // System.out.println("updatedChangeContext = " + changeContext);
460            Entity newChangeContext = _computeBound(changeContext,
461                    oldChangeContext);
462
463            // System.out.println("newChangeContext = " + newChangeContext);
464            if (newChangeContext != oldChangeContext) {
465                _variableToChangeContext.put(variable, newChangeContext);
466
467                return true;
468            }
469            return false;
470        } else {
471            _variableToChangeContext.put(variable, changeContext);
472            return true;
473        }
474    }
475
476    // Return the entity which is contained by the other in the ptolemy
477    // hierarchy.  If neither contains the other, then throw an
478    // exception.  If either entity is null (corresponding to a change context
479    // which doesn't exist, then return null.
480    private final Entity _computeBound(Entity entity1, Entity entity2) {
481        if (entity1 == null || entity2 == null) {
482            return null;
483        }
484        if (entity2.equals(entity1)) {
485            return entity1;
486        }
487        if (entity2.deepContains(entity1)) {
488            return entity1;
489        } else if (entity1.deepContains(entity2)) {
490            return entity2;
491        } else {
492            return null;
493        }
494    }
495
496    ///////////////////////////////////////////////////////////////////
497    ////                         private variables                 ////
498
499    private DirectedGraph _dependencyGraph;
500
501    private Map _variableToChangeContext;
502}