001/* This director extends FSMDirector by consuming only input tokens
002 that are needed in the current state.
003
004 Copyright (c) 2004-2014 The Regents of the University of California.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027 */
028package ptolemy.domains.modal.kernel;
029
030import java.util.HashSet;
031import java.util.Iterator;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036import ptolemy.actor.Actor;
037import ptolemy.actor.CompositeActor;
038import ptolemy.actor.IOPort;
039import ptolemy.actor.TypedActor;
040import ptolemy.actor.util.DFUtilities;
041import ptolemy.data.expr.ASTPtAssignmentNode;
042import ptolemy.data.expr.ASTPtRootNode;
043import ptolemy.data.expr.ParseTreeFreeVariableCollector;
044import ptolemy.data.expr.ParserScope;
045import ptolemy.data.expr.PtParser;
046import ptolemy.kernel.CompositeEntity;
047import ptolemy.kernel.util.IllegalActionException;
048import ptolemy.kernel.util.NameDuplicationException;
049import ptolemy.kernel.util.Workspace;
050
051///////////////////////////////////////////////////////////////////
052//// NonStrictFSMDirector
053
054/**
055 This director extends FSMDirector by consuming only input tokens that
056 are needed in the current state. An input port will consume at most one
057 token if:
058 <p>
059 1. The port is referred by any guard expression of the preemptive
060 transitions leaving the current state, the output actions
061 and/or set actions of the enabled transition.
062 <p>
063 2. No preemptive transition is enabled and the port is referred by
064 the refinements of the current state, any guard expression of the
065 nonpreemptive transitions leaving the current state, the output
066 actions and/or set actions of the enabled transition.
067 <p>
068 A port is said to be referred by a guard/output action/set action
069 expression of a transition if the port name appears in that expression.
070 A port is said to be referred by a state refinement if the it is
071 not a dangling port and has a consumption rate greater than zero in
072 the refinement.
073 <p>
074 FIXME: This is highly preliminary. Missing capabilities:
075 FIXME: Currently this director uses the default receiver of FSMDirector,
076 which is a mailbox, so there is no way to consume more than one token.
077 This director could use a different receiver and support a syntax in
078 the guard expression language to support consumption of more than one
079 token.
080 FIXME: This director does not support immediate transitions.
081
082 @author Ye Zhou
083 @version $Id$
084 @since Ptolemy II 8.0
085 @Pt.ProposedRating Red (zhouye)
086 @Pt.AcceptedRating Red (cxh)
087 */
088public class NonStrictFSMDirector extends FSMDirector {
089    /** Construct a director in the given container with the given name.
090     *  The container argument must not be null, or a
091     *  NullPointerException will be thrown.
092     *  If the name argument is null, then the name is set to the
093     *  empty string. Increment the version number of the workspace.
094     *  @param container Container of this director.
095     *  @param name Name of this director.
096     *  @exception IllegalActionException If the name has a period in it, or
097     *   the director is not compatible with the specified container.
098     *  @exception NameDuplicationException If the container not a
099     *   CompositeActor and the name collides with an entity in the container.
100     */
101    public NonStrictFSMDirector(CompositeEntity container, String name)
102            throws IllegalActionException, NameDuplicationException {
103        super(container, name);
104    }
105
106    ///////////////////////////////////////////////////////////////////
107    ////                         public methods                    ////
108
109    /** Clone the actor into the specified workspace. This calls the
110     *  base class and then sets the attribute public members to refer
111     *  to the attributes of the new actor.
112     *  @param workspace The workspace for the new actor.
113     *  @return A new FSMActor.
114     *  @exception CloneNotSupportedException If a derived class contains
115     *   an attribute that cannot be cloned.
116     */
117    @Override
118    public Object clone(Workspace workspace) throws CloneNotSupportedException {
119        NonStrictFSMDirector newObject = (NonStrictFSMDirector) super.clone(
120                workspace);
121        newObject._nonpreemptiveTransitionsInputs = new HashSet();
122        newObject._outputActionReferredInputPorts = new HashSet();
123        newObject._preemptiveTransitionsInputs = new HashSet();
124        newObject._referredInputPorts = new HashSet();
125        newObject._refinementReferredInputPorts = new HashSet();
126        newObject._setActionReferredInputPorts = new HashSet();
127        return newObject;
128    }
129
130    /** Fire the modal model. The preemptive transitions from the current
131     *  state are examined. If there is more than one transition enabled,
132     *  an exception is thrown. If there is exactly one preemptive transition
133     *  enabled, then it is chosen. Get additional input ports referred by
134     *  the output actions and set actions of the enabled transition and
135     *  executed the output actions. The refinements of the current state will
136     *  not be fired.
137     *  <p>
138     *  If no preemptive transition is enabled, get additional input ports
139     *  referred by the refinements of the current state and transfer at most
140     *  one token from these input ports. Fire the refinements. After this,
141     *  get additional input ports referred by the nonpreemptive transitions
142     *  from the current state and transfer at most one token from these input
143     *  ports. Then examine the nonpreemptive transitions. If there is more
144     *  than one transition enabled, an exception is thrown. If there is
145     *  exactly one nonpreemptive transition enabled, then it is chosen. Get
146     *  additional input ports referred by the output actions and set actions
147     *  of the enabled transition and executed the output actions.
148     *  @exception IllegalActionException If the super class throws it.
149     */
150    @Override
151    public void fire() throws IllegalActionException {
152        FSMActor controller = getController();
153        controller.readInputs();
154
155        CompositeActor container = (CompositeActor) getContainer();
156        List inputPortList = container.inputPortList();
157        State currentState = controller.currentState();
158        List transitionList = currentState.outgoingPort.linkedRelationList();
159
160        // Choose a preemptive transition
161        List enabledTransitions = controller.enabledTransitions(transitionList,
162                true, false);
163
164        // Ensure that if there are multiple enabled transitions, all of them
165        // must be nondeterministic.
166        if (enabledTransitions.size() > 1) {
167            Iterator transitions = enabledTransitions.iterator();
168
169            while (transitions.hasNext()) {
170                Transition enabledTransition = (Transition) transitions.next();
171
172                if (!enabledTransition.isNondeterministic()) {
173                    throw new MultipleEnabledTransitionsException(
174                            controller.currentState(),
175                            "Multiple enabled transitions found but "
176                                    + enabledTransition.getName()
177                                    + " is deterministic.");
178                }
179            }
180        }
181
182        Transition enabledTransition = null;
183
184        // Randomly choose one transition from the list of the
185        // enabled trnasitions.
186        int length = enabledTransitions.size();
187
188        if (length != 0) {
189            // Since the size of the list of enabled transitions usually (almost
190            // always) is less than the maximum value of integer. We can safely
191            // do the cast from long to int in the following statement.
192            int randomChoice = (int) Math.floor(Math.random() * length);
193
194            // There is tiny chance that randomChoice equals length.
195            // When this happens, we deduct 1 from the randomChoice.
196            if (randomChoice == length) {
197                randomChoice--;
198            }
199
200            enabledTransition = (Transition) enabledTransitions
201                    .get(randomChoice);
202        }
203
204        controller.setLastChosenTransition(enabledTransition);
205
206        if (enabledTransition == null) {
207            // Get the inputs needed by the refinement.
208            Actor[] actors = currentState.getRefinement();
209            getRefinementReferredInputPorts(currentState);
210
211            // Transfer additional inputs needed by the refinement.
212            for (int i = 0; i < inputPortList.size(); i++) {
213                IOPort port = (IOPort) inputPortList.get(i);
214
215                if (_refinementReferredInputPorts.contains(port)
216                        && !_referredInputPorts.contains(port)) {
217                    super.transferInputs(port);
218                    controller.readInputs();
219                    _referredInputPorts.add(port);
220                }
221            }
222
223            // Fire the refinement.
224            if (actors != null) {
225                for (int i = 0; i < actors.length; ++i) {
226                    if (_stopRequested) {
227                        break;
228                    }
229
230                    if (actors[i].prefire()) {
231                        actors[i].fire();
232                        actors[i].postfire();
233                    }
234                }
235            }
236
237            controller.readOutputsFromRefinement();
238
239            // Get inputs needed by the nonpreemptive transitions.
240            getNonpreemptiveTransitionsReferredInputPorts(currentState);
241
242            // Transfer additional inputs needed by the refinement.
243            for (int i = 0; i < inputPortList.size(); i++) {
244                IOPort port = (IOPort) inputPortList.get(i);
245
246                if (_nonpreemptiveTransitionsInputs.contains(port)
247                        && !_referredInputPorts.contains(port)) {
248                    super.transferInputs(port);
249                    controller.readInputs();
250                    _referredInputPorts.add(port);
251                }
252            }
253
254            // Choose an error transition
255            enabledTransitions = controller.enabledTransitions(
256                    currentState.errorTransitionList(), false, false);
257            if (enabledTransitions.size() == 0) {
258                //choose a nonpremptive transition
259                enabledTransitions = controller
260                        .enabledTransitions(transitionList, false, false);
261            }
262
263            // Ensure that if there are multiple enabled transitions, all of them
264            // must be nondeterministic.
265            if (enabledTransitions.size() > 1) {
266                Iterator transitions = enabledTransitions.iterator();
267
268                while (transitions.hasNext()) {
269                    Transition transition = (Transition) transitions.next();
270
271                    if (!transition.isNondeterministic()) {
272                        throw new MultipleEnabledTransitionsException(
273                                controller.currentState(),
274                                "Multiple enabled transitions found but "
275                                        + transition.getName()
276                                        + " is deterministic.");
277                    }
278                }
279            }
280
281            // Randomly choose one transition from the list of the
282            // enabled trnasitions.
283            length = enabledTransitions.size();
284
285            if (length != 0) {
286                // Since the size of the list of enabled transitions usually (almost
287                // always) is less than the maximum value of integer. We can safely
288                // do the cast from long to int in the following statement.
289                int randomChoice = (int) Math.floor(Math.random() * length);
290
291                // There is tiny chance that randomChoice equals length.
292                // When this happens, we deduct 1 from the randomChoice.
293                if (randomChoice == length) {
294                    randomChoice--;
295                }
296
297                enabledTransition = (Transition) enabledTransitions
298                        .get(randomChoice);
299            }
300            controller.setLastChosenTransition(enabledTransition);
301        }
302
303        if (enabledTransition != null) {
304            // Get additional inputs needed for output actions and set actions
305            // of the enabled transition.
306            getOutputActionsReferredInputPorts(enabledTransition);
307            getSetActionsReferredInputPorts(enabledTransition);
308
309            for (int i = 0; i < inputPortList.size(); i++) {
310                IOPort port = (IOPort) inputPortList.get(i);
311
312                if (_outputActionReferredInputPorts.contains(port)
313                        && !_referredInputPorts.contains(port)) {
314                    super.transferInputs(port);
315                    controller.readInputs();
316                    _referredInputPorts.add(port);
317                }
318            }
319
320            controller.readInputs();
321
322            // execute output actions.
323            Iterator actions = enabledTransition.choiceActionList().iterator();
324
325            while (actions.hasNext()) {
326                Action action = (Action) actions.next();
327                action.execute();
328            }
329
330            // Get additional input ports needed by set actions of
331            // the enabeld transition.
332            for (int i = 0; i < inputPortList.size(); i++) {
333                IOPort port = (IOPort) inputPortList.get(i);
334
335                if (_setActionReferredInputPorts.contains(port)
336                        && !_referredInputPorts.contains(port)) {
337                    super.transferInputs(port);
338                    controller.readInputs();
339                    _referredInputPorts.add(port);
340                }
341            }
342
343            controller.readInputs();
344        }
345
346        controller.setLastChosenTransition(enabledTransition);
347    }
348
349    /** Given a state, get a set of input ports referred in the guards of
350     *  the preemptive transitions leaving that state.
351     *  @param state The given state.
352     *  @exception IllegalActionException If there is no controller or
353     *   if any guard expression is illegal.
354     */
355    public void getNonpreemptiveTransitionsReferredInputPorts(State state)
356            throws IllegalActionException {
357        List nonpreemptiveTransitionList = state.nonpreemptiveTransitionList();
358
359        _nonpreemptiveTransitionsInputs = getTransitionReferredInputPorts(
360                nonpreemptiveTransitionList);
361    }
362
363    /** Given a transition, get a set of input ports referred in the
364     *  outputActions of that transition.
365     *  @param transition The transition.
366     *  @exception IllegalActionException If there is no controller or if
367     *  the outputActions is illegal.
368     */
369    public void getOutputActionsReferredInputPorts(Transition transition)
370            throws IllegalActionException {
371        _outputActionReferredInputPorts.clear();
372
373        String string = transition.outputActions.getExpression();
374        PtParser parser = new PtParser();
375        ASTPtRootNode parseTree;
376        ParseTreeFreeVariableCollector variableCollector = new ParseTreeFreeVariableCollector();
377        FSMActor controller = getController();
378        ParserScope scope = controller.getPortScope();
379
380        if (!string.equals("")) {
381            Map map = parser.generateAssignmentMap(string);
382            Set set /* Dead Local Store: = new HashSet()*/;
383
384            for (Iterator names = map.entrySet().iterator(); names.hasNext();) {
385                Map.Entry entry = (Map.Entry) names.next();
386                ASTPtAssignmentNode node = (ASTPtAssignmentNode) entry
387                        .getValue();
388                parseTree = node.getExpressionTree();
389                set = variableCollector.collectFreeVariables(parseTree, scope);
390                getReferredInputPorts(set, _outputActionReferredInputPorts);
391            }
392        }
393    }
394
395    /** Given a state, get a set of input ports referred in the guards of
396     *  the preemptive transitions leaving that state.
397     *  @param state The given state.
398     *  @exception IllegalActionException If there is no controller or
399     *   if any guard expression is illegal.
400     */
401    public void getPreemptiveTransitionsReferredInputPorts(State state)
402            throws IllegalActionException {
403        List preemptiveTransitionList = state.preemptiveTransitionList();
404
405        _preemptiveTransitionsInputs = getTransitionReferredInputPorts(
406                preemptiveTransitionList);
407    }
408
409    /** Given a set of ports, get those that are input ports of the container
410     *  and put them in the indicated referred set.
411     *  @param portSet The given set of ports
412     *  @param referredInputPorts The referred set.
413     */
414    public void getReferredInputPorts(Set portSet, Set referredInputPorts) {
415        CompositeActor container = (CompositeActor) getContainer();
416        List inputPortList = container.inputPortList();
417
418        for (int i = 0; i < inputPortList.size(); i++) {
419            IOPort inputPort = (IOPort) inputPortList.get(i);
420
421            if (portSet.contains(inputPort.getName())) {
422                referredInputPorts.add(inputPort);
423            }
424        }
425    }
426
427    /** Given a state, get a set of input ports referred by the refinements
428     *  of that state.
429     * @param state The given state.
430     * @exception IllegalActionException If refinement with given name is not
431     *  found, or if the port rate does not contain a valid expression.
432     */
433    public void getRefinementReferredInputPorts(State state)
434            throws IllegalActionException {
435        _refinementReferredInputPorts.clear();
436
437        TypedActor[] refinements = state.getRefinement();
438        CompositeActor container = (CompositeActor) getContainer();
439
440        if (refinements != null) {
441            for (TypedActor refinement : refinements) {
442                Iterator inputPorts = refinement.inputPortList().iterator();
443
444                while (inputPorts.hasNext()) {
445                    IOPort inputPort = (IOPort) inputPorts.next();
446
447                    if (inputPort.isOutsideConnected()
448                            && DFUtilities.getRate(inputPort) > 0) {
449                        Iterator inputPortsOutside = inputPort
450                                .deepConnectedInPortList().iterator();
451
452                        while (inputPortsOutside.hasNext()) {
453                            IOPort inputPortOutside = (IOPort) inputPortsOutside
454                                    .next();
455
456                            if (inputPortOutside.getContainer() == container
457                                    && !_refinementReferredInputPorts
458                                            .contains(inputPortOutside)) {
459                                _refinementReferredInputPorts
460                                        .add(inputPortOutside);
461                            }
462                        }
463                    }
464                }
465            }
466        }
467    }
468
469    /** Given a transition, get a set of input ports referred in the set
470     *  actions of that transition.
471     *  @param transition The given transition.
472     *  @exception IllegalActionException If there is no controller or
473     *   if any set action expression is illegal.
474     */
475    public void getSetActionsReferredInputPorts(Transition transition)
476            throws IllegalActionException {
477        _setActionReferredInputPorts.clear();
478
479        String string = transition.setActions.getExpression();
480        PtParser parser = new PtParser();
481        ASTPtRootNode parseTree;
482        ParseTreeFreeVariableCollector variableCollector = new ParseTreeFreeVariableCollector();
483        FSMActor controller = getController();
484        ParserScope scope = controller.getPortScope();
485
486        if (!string.equals("")) {
487            Map map = parser.generateAssignmentMap(string);
488            Set set /* Dead Local Store: = new HashSet()*/;
489
490            for (Iterator names = map.entrySet().iterator(); names.hasNext();) {
491                Map.Entry entry = (Map.Entry) names.next();
492                ASTPtAssignmentNode node = (ASTPtAssignmentNode) entry
493                        .getValue();
494                parseTree = node.getExpressionTree();
495                set = variableCollector.collectFreeVariables(parseTree, scope);
496                getReferredInputPorts(set, _setActionReferredInputPorts);
497            }
498        }
499    }
500
501    /** Given a list of transitions, get a set of referred input ports
502     *  in the guard expressions of all the transitions leaving this state.
503     *  @param transitionList The list of Transitions.
504     *  @return A set of input ports referred by the guard expressions
505     *   of the given transition list.
506     *  @exception IllegalActionException If there is no controller or if
507     *   the guard expression is illegal.
508     */
509    public Set getTransitionReferredInputPorts(List transitionList)
510            throws IllegalActionException {
511        Set transitionsReferredInputPorts = new HashSet();
512
513        Iterator transitions = transitionList.iterator();
514
515        while (transitions.hasNext()) {
516            Transition transition = (Transition) transitions.next();
517            String string = transition.getGuardExpression();
518
519            if (string.equals("") && !transition.isErrorTransition()) {
520                throw new IllegalActionException(this, "guard expression on "
521                        + transition.getName() + "is null!");
522            }
523            if (!transition.isErrorTransition()) {
524                PtParser parser = new PtParser();
525                ASTPtRootNode parseTree = parser.generateParseTree(string);
526                ParseTreeFreeVariableCollector variableCollector = new ParseTreeFreeVariableCollector();
527                FSMActor controller = getController();
528                ParserScope scope = controller.getPortScope();
529                Set set = variableCollector.collectFreeVariables(parseTree,
530                        scope);
531                getReferredInputPorts(set, transitionsReferredInputPorts);
532            }
533        }
534
535        return transitionsReferredInputPorts;
536    }
537
538    /** Initialize the director. Get the referred input ports in the guard
539     *  expressions of all preemptive transitions leaving the initial state.
540     *  @exception IllegalActionException If the super class throws it.
541     */
542    @Override
543    public void initialize() throws IllegalActionException {
544        super.initialize();
545
546        FSMActor controller = getController();
547        getPreemptiveTransitionsReferredInputPorts(
548                controller.getInitialState());
549        _referredInputPorts.clear();
550        _referredInputPorts.addAll(_preemptiveTransitionsInputs);
551    }
552
553    /** Call the postfire() method of the super class. Get the referred
554     *  input ports in the guard expressions of all preemptive transitions
555     *  that go out from the current state.
556     *  @exception IllegalActionException If the super class throws it.
557     */
558    @Override
559    public boolean postfire() throws IllegalActionException {
560        boolean postfireValue = super.postfire();
561        FSMActor controller = getController();
562        getPreemptiveTransitionsReferredInputPorts(controller.currentState());
563        _referredInputPorts.clear();
564        _referredInputPorts.addAll(_preemptiveTransitionsInputs);
565        return postfireValue;
566    }
567
568    /** Override the super class by only transferring inputs for those
569     *  input ports that are referred by the guard expressions of the
570     *  preemptive transitions leaving the current state.
571     */
572    @Override
573    public boolean transferInputs(IOPort port) throws IllegalActionException {
574        if (_preemptiveTransitionsInputs.contains(port)) {
575            return super.transferInputs(port);
576        } else {
577            return true;
578        }
579    }
580
581    ///////////////////////////////////////////////////////////////////
582    ////                         private variables                 ////
583    // A set of input ports that are referred by the guard expressions
584    // of the nonpreemptive transitions leaving the current state.
585    private Set _nonpreemptiveTransitionsInputs = new HashSet();
586
587    // A set of input ports that are referred by the output actions of
588    // the enabled transition.
589    private Set _outputActionReferredInputPorts = new HashSet();
590
591    // A set of input ports that are referred by the guard expressions
592    // of the preemptive transitions leaving the current state.
593    private Set _preemptiveTransitionsInputs = new HashSet();
594
595    // A set of input ports that are referred.
596    private Set _referredInputPorts = new HashSet();
597
598    // A set of input ports that are referred by the refinements
599    // of the current state.
600    private Set _refinementReferredInputPorts = new HashSet();
601
602    // A set of input ports that are referred by the set actions of
603    // the enabled transition.
604    private Set _setActionReferredInputPorts = new HashSet();
605}