001/* An actor that produces a copy of the most recent input each time
002 the trigger input receives an event.
003
004 Copyright (c) 1998-2018 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
028 */
029package ptolemy.domains.de.lib;
030
031import java.util.Set;
032
033import ptolemy.actor.TypedIOPort;
034import ptolemy.actor.lib.Sampler;
035import ptolemy.actor.lib.Transformer;
036import ptolemy.data.Token;
037import ptolemy.data.expr.Parameter;
038import ptolemy.graph.Inequality;
039import ptolemy.kernel.CompositeEntity;
040import ptolemy.kernel.util.Attribute;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.InternalErrorException;
043import ptolemy.kernel.util.NameDuplicationException;
044import ptolemy.kernel.util.StringAttribute;
045import ptolemy.kernel.util.Workspace;
046
047///////////////////////////////////////////////////////////////////
048//// MostRecent
049
050/**
051 Output the most recent input token when the <i>trigger</i> port
052 receives a token.  If no token has been received on the <i>input</i>
053 port when a token is received on the <i>trigger</i> port, then the
054 value of the <i>initialValue</i> parameter is produced.  If, however,
055 the <i>initialValue</i> parameter contains no value, then no output is
056 produced.  The inputs can be of any token type, but the <i>output</i>
057 port is constrained to be of a type at least that of the <i>input</i>
058 port and the <i>initialValue</i> parameter (if it has a value).
059
060 <p> Both the <i>input</i> port and the <i>output</i> port are multiports.
061 Generally, their widths should match. Otherwise, if the width of the
062 <i>input</i> is greater than the width of the <i>output</i>, the extra
063 input tokens will not appear on any output, although they will be
064 consumed from the input port. If the width of the <i>output</i> is
065 greater than that of the <i>input</i>, then the last few channels of
066 the <i>output</i> will never emit tokens.
067
068 <p> The <i>trigger</i> port is a multiport. Whenever a trigger is received
069 on any channel the actor fires and produces an output. Multiple triggers
070 with the same timestamp are considered as one trigger.
071
072 <p> Note: If the width of the input changes during execution, then the
073 most recent inputs are forgotten, as if the execution of the model
074 were starting over.
075
076 <p> This actor is similar to the Inhibit actor in that it modifies a
077 stream of events based on the presence or absence of events from
078 another input.  This actor reacts to the presence of the other event,
079 whereas Inhibit reacts to the absence of it.
080
081 <p> This actor is different from the Register actor in that the input
082 tokens are consumed from the input ports before the outputs are generated.
083 Note that this actor is also different from the
084 {@link Sampler} actor, which produces the <i>current</i> input on the
085 output when a <i>trigger</i> input is present, rather than the most
086 recently received input signal.
087
088 @author Jie Liu, Edward A. Lee, Steve Neuendorffer, Elaine Cheong
089 @version $Id$
090 @since Ptolemy II 10.0
091 @Pt.ProposedRating Yellow (eal)
092 @Pt.AcceptedRating Yellow (eal)
093 @see ptolemy.domains.de.lib.Inhibit
094 @see ptolemy.domains.de.lib.Register
095 */
096public class MostRecent extends Transformer {
097    /** Construct an actor with the given container and name.
098     *  @param container The container.
099     *  @param name The name of this actor.
100     *  @exception IllegalActionException If the actor cannot be contained
101     *   by the proposed container.
102     *  @exception NameDuplicationException If the container already has an
103     *   actor with this name.
104     */
105    public MostRecent(CompositeEntity container, String name)
106            throws NameDuplicationException, IllegalActionException {
107        super(container, name);
108        input.setMultiport(true);
109        output.setMultiport(true);
110        output.setTypeAtLeast(input);
111        trigger = new TypedIOPort(this, "trigger", true, false);
112        trigger.setMultiport(true);
113
114        // Width constraint. Not bidirectional to not break any existing models.
115        output.setWidthEquals(input, false);
116
117        // Leave type undeclared.
118        initialValue = new Parameter(this, "initialValue");
119
120        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-20\" "
121                + "width=\"60\" height=\"40\" " + "style=\"fill:white\"/>\n"
122                + "<polyline points=\"0,20 0,0\"/>\n"
123                + "<polyline points=\"-30,-0 -10,0 10,-7\"/>\n"
124                + "<polyline points=\"10,0 30,0\"/>\n" + "</svg>\n");
125
126        StringAttribute cardinality = new StringAttribute(trigger, "_cardinal");
127        cardinality.setExpression("SOUTH");
128    }
129
130    ///////////////////////////////////////////////////////////////////
131    ////                     ports and parameters                  ////
132
133    /** The trigger port, which has undeclared type. If this port
134     *  receives a token, then the most recent token from the
135     *  <i>input</i> port will be emitted on the <i>output</i> port.
136     */
137    public TypedIOPort trigger;
138
139    /** The value that is output when no input has yet been received.
140     *  If this is changed during execution, then the output will match
141     *  the new value until another input is received.
142     *  The type should be the same as the input port.
143     *  @see #typeConstraints()
144     */
145    public Parameter initialValue;
146
147    ///////////////////////////////////////////////////////////////////
148    ////                         public methods                    ////
149
150    /** If the <i>initialValue</i> parameter is the argument, then
151     *  reset the current output to match the new value.
152     *  @param attribute The attribute that changed.
153     *  @exception IllegalActionException If the change is not acceptable
154     *   to this container (not thrown in this base class).
155     */
156    @Override
157    public void attributeChanged(Attribute attribute)
158            throws IllegalActionException {
159
160        if (attribute == initialValue) {
161            if (initialValue.getToken() != null) {
162                int width = 1;
163                // Calling input.getWidth() when the model is not being
164                // executed can cause Exceptions because the width cannot
165                // be resolved. This method is called also, for instance, when a
166                // class containing a MostRecent actor is saved. In this case,
167                // the model is not executed and the width is irrelevant.
168                if (_initializeDone) {
169                    width = input.getWidth();
170                }
171                if (width < 1) {
172                    width = 1;
173                }
174                _lastInputs = new Token[width];
175                for (int i = 0; i < width; i++) {
176                    _lastInputs[i] = initialValue.getToken();
177                }
178
179            } else {
180                _lastInputs = null;
181            }
182        } else {
183            super.attributeChanged(attribute);
184        }
185    }
186
187    /** Clone the actor into the specified workspace. This calls the
188     *  base class and then sets the ports.
189     *  @param workspace The workspace for the new object.
190     *  @return A new actor.
191     *  @exception CloneNotSupportedException If a derived class has
192     *   has an attribute that cannot be cloned.
193     */
194    @Override
195    public Object clone(Workspace workspace) throws CloneNotSupportedException {
196        MostRecent newObject = (MostRecent) super.clone(workspace);
197        newObject.output.setTypeAtLeast(newObject.input);
198
199        // Width constraint. Not bidirectional to not break any existing models.
200        newObject.output.setWidthEquals(newObject.input, false);
201
202        // This is not strictly needed (since it is always recreated
203        // in preinitialize) but it is safer.
204        newObject._lastInputs = null;
205
206        return newObject;
207    }
208
209    /** Consume all the tokens in the input ports and record them.
210     *  If there is a token in the <i>trigger</i> port, emit the most
211     *  recent token from the <i>input</i> port. If there has been no
212     *  input token, but the <i>initialValue</i> parameter has been
213     *  set, emit the value of the <i>initialValue</i> parameter.
214     *  Otherwise, emit nothing.
215     *  @exception IllegalActionException If there is no director.
216     */
217    @Override
218    public void fire() throws IllegalActionException {
219        super.fire();
220
221        int inputWidth = input.getWidth();
222        int outputWidth = output.getWidth();
223        int commonWidth = Math.min(inputWidth, outputWidth);
224
225        // If the <i>initialValue</i> parameter was not set, or if the
226        // width of the input has changed.
227        if (_lastInputs == null || _lastInputs.length != inputWidth) {
228            _lastInputs = new Token[inputWidth];
229        }
230
231        readInputs(commonWidth, inputWidth);
232
233        sendOutputIfTriggered(commonWidth);
234
235    }
236
237    /** If there is no input on the <i>trigger</i> port, return
238     *  false, indicating that this actor does not want to fire.
239     *  This has the effect of leaving input values in the input
240     *  ports, if there are any.
241     *  @exception IllegalActionException If there is no director.
242     */
243    @Override
244    public boolean prefire() throws IllegalActionException {
245        super.prefire();
246
247        // If the trigger input is not connected, never fire.
248        if (trigger.isOutsideConnected()) {
249            boolean hasToken = false;
250            for (int j = 0; j < trigger.getWidth(); j++) {
251                if (trigger.hasToken(j)) {
252                    hasToken = true;
253                    break;
254                }
255            }
256            return hasToken;
257        } else {
258            return false;
259        }
260    }
261
262    /** Clear the cached input tokens.
263     *  @exception IllegalActionException If there is no director.
264     */
265    @Override
266    public void initialize() throws IllegalActionException {
267        if (initialValue.getToken() != null) {
268            _lastInputs = new Token[input.getWidth()];
269
270            for (int i = 0; i < input.getWidth(); i++) {
271                _lastInputs[i] = initialValue.getToken();
272            }
273        } else {
274            _lastInputs = null;
275        }
276        _initializeDone = true;
277        super.initialize();
278    }
279
280    /** Wrapup and reset variables.
281     */
282    @Override
283    public void wrapup() throws IllegalActionException {
284        super.wrapup();
285        _initializeDone = false;
286    }
287
288    /** Override the method in the base class so that the type
289     *  constraint for the <i>initialValue</i> parameter will be set
290     *  if it contains a value.
291     *  @return a list of Inequality objects.
292     *  @see ptolemy.graph.Inequality
293     */
294    /*    public Set<Inequality> typeConstraints() {
295            Set<Inequality> typeConstraints = super.typeConstraints();
296
297            try {
298                if (initialValue.getToken() != null) {
299                    // Set type of initialValue to be equal to input type
300                    Inequality ineq = new Inequality(initialValue.getTypeTerm(),
301                            input.getTypeTerm());
302                    typeConstraints.add(ineq);
303                    ineq = new Inequality(input.getTypeTerm(),
304                            initialValue.getTypeTerm());
305
306                    typeConstraints.add(ineq);
307                }
308            } catch (IllegalActionException ex) {
309                // Errors in the initialValue parameter should already
310                // have been caught in getAttribute() method of the base
311                // class.
312                throw new InternalErrorException("Bad initialValue value!");
313            }
314
315            return typeConstraints;
316        }
317     */
318    /**
319     * Adds two inequalities to the set returned by the overridden method that
320     * together constrain the input to be equal to the type of the initial
321     * value.
322     */
323    @Override
324    public Set<Inequality> _containedTypeConstraints() {
325        Set<Inequality> result = super._containedTypeConstraints();
326        try {
327            // Set type of initialValue to be equal to input type
328            if (initialValue.getToken() != null) {
329                result.add(new Inequality(initialValue.getTypeTerm(),
330                        input.getTypeTerm()));
331                result.add(new Inequality(input.getTypeTerm(),
332                        initialValue.getTypeTerm()));
333            }
334        } catch (IllegalActionException ex) {
335            // Errors in the initialValue parameter should already
336            // have been caught in getAttribute() method of the base
337            // class.
338            throw new InternalErrorException("Bad initialValue value!");
339        }
340
341        return result;
342    }
343
344    ///////////////////////////////////////////////////////////////////
345    ////                         private variables                 ////
346
347    /** The recorded inputs last seen. */
348    protected Token[] _lastInputs;
349
350    /** Consume inputs and save them. Discard inputs on input channels
351     *  that do not have corresponding output channels.
352     *  @param commonWidth The minimum of the input and the output width.
353     *  @param inputWidth The width of the input port.
354     *  @exception IllegalActionException Thrown if port tokens cannot be accessed.
355     */
356    protected void readInputs(int commonWidth, int inputWidth)
357            throws IllegalActionException {
358        // Consume the inputs we save.
359        for (int i = 0; i < commonWidth; i++) {
360            while (input.hasNewToken(i)) {
361                _lastInputs[i] = input.get(i);
362            }
363        }
364
365        // Consume the inputs we don't save.
366        for (int i = commonWidth; i < inputWidth; i++) {
367            while (input.hasNewToken(i)) {
368                input.get(i);
369            }
370        }
371    }
372
373    /** Send output tokens if any input on the trigger port has a token.
374     *  All trigger tokens are consumed.
375     *  @param commonWidth The minimum of the input and the output port width.
376     *  @exception IllegalActionException Thrown if the width or the token of
377     *      the trigger port cannot be accessed or if tokens cannot be sent on
378     *      the output port.
379     */
380    protected void sendOutputIfTriggered(int commonWidth)
381            throws IllegalActionException {
382        // If the trigger input is not known, return without
383        // doing anything.
384        if (!trigger.isKnown()) {
385            return;
386        }
387        // If we have a trigger...
388        boolean triggered = false;
389        for (int j = 0; j < trigger.getWidth(); j++) {
390            if (trigger.hasToken(j)) {
391                // Consume the trigger token.
392                trigger.get(j);
393                triggered = true;
394            }
395        }
396
397        for (int i = 0; i < commonWidth; i++) {
398            if (triggered) {
399                // Do not output anything if the <i>initialValue</i>
400                // parameter was not set and this actor has not
401                // received any inputs.
402                if (_lastInputs[i] != null) {
403                    // Output the most recent token, assuming the
404                    // receiver has a FIFO behavior.
405                    output.send(i, _lastInputs[i]);
406                }
407            } else {
408                // Indicate that the output is absent so that this
409                // be used in an SR or Continuous feedback loop.
410                output.sendClear(i);
411            }
412        }
413    }
414
415    private boolean _initializeDone = false;
416}