001/* A DE actor to compare the time stamps of events at its two input ports, and
002 output the difference.
003
004 Copyright (c) 2008-2015 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.Iterator;
032import java.util.LinkedList;
033import java.util.List;
034
035import ptolemy.actor.TypedIOPort;
036import ptolemy.data.BooleanToken;
037import ptolemy.data.DoubleToken;
038import ptolemy.data.expr.Parameter;
039import ptolemy.data.type.BaseType;
040import ptolemy.domains.de.kernel.DEActor;
041import ptolemy.domains.de.kernel.DEDirector;
042import ptolemy.kernel.CompositeEntity;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.NameDuplicationException;
045import ptolemy.kernel.util.Workspace;
046
047//////////////////////////////////////////////////////////////////////////
048//// TimeCompare
049
050/**
051 A DE actor to compare the time stamps of events at its two input ports, and
052 output the difference. Every time an event can be processed either at input
053 port <code>input1</code> or at <code>input2</code>, the event is consumed. The
054 value that the event carries is insignificant, but the time stamp of the event
055 is recorded in a local list. Time stamps received at the two input ports are
056 stored in two different lists. Every time when both lists have data, the
057 difference between the top elements of the lists is obtained, and is sent to
058 the output port. This done by subtracting the time stamp of every top event in
059 the list for <code>input2</code> with the time stamp of every top event in the
060 list for <code>input1</code>.
061 <p>
062 This actor could potentially consume an infinite amount of memory if the
063 arrival rates of events at the two input ports are different, because one of
064 the lists keeps growing.
065
066 @author Thomas Huining Feng
067 @version $Id$
068 @since Ptolemy II 8.0
069 @Pt.ProposedRating Red (tfeng)
070 @Pt.AcceptedRating Red (tfeng)
071 */
072public class TimeCompare extends DEActor {
073
074    /** Construct an actor with the specified container and name.
075     *  This is protected because there is no reason to create an instance
076     *  of this class, but derived classes will want to invoke the
077     *  constructor of the superclass.
078     *  @param container The container.
079     *  @param name The name.
080     *  @exception IllegalActionException If the entity cannot be contained
081     *   by the proposed container.
082     *  @exception NameDuplicationException If the container already has an
083     *   actor with this name.
084     */
085    public TimeCompare(CompositeEntity container, String name)
086            throws NameDuplicationException, IllegalActionException {
087        super(container, name);
088
089        nonnegative = new Parameter(this, "nonnegative");
090        nonnegative.setTypeEquals(BaseType.BOOLEAN);
091        nonnegative.setToken(BooleanToken.FALSE);
092
093        input1 = new TypedIOPort(this, "input1", true, false);
094        input2 = new TypedIOPort(this, "input2", true, false);
095        output = new TypedIOPort(this, "output", false, true);
096        output.setTypeEquals(BaseType.DOUBLE);
097    }
098
099    /** Clone this actor into the specified workspace. The new actor is
100     *  <i>not</i> added to the directory of that workspace (you must do this
101     *  yourself if you want it there).
102     *  The result is a new actor with the same ports as the original, but
103     *  no connections and no container.  A container must be set before
104     *  much can be done with this actor.
105     *
106     *  @param workspace The workspace for the cloned object.
107     *  @exception CloneNotSupportedException If cloned ports cannot have
108     *   as their container the cloned entity (this should not occur), or
109     *   if one of the attributes cannot be cloned.
110     *  @return A new ComponentEntity.
111     */
112    @Override
113    public Object clone(Workspace workspace) throws CloneNotSupportedException {
114        TimeCompare newObject = (TimeCompare) super.clone(workspace);
115        newObject._input1TimeStamps = new LinkedList<Double>();
116        newObject._input2TimeStamps = new LinkedList<Double>();
117        return newObject;
118    }
119
120    /** Fire this actor once. If there are events at its input ports, they are
121     *  immediately consumed, and their time stamps are recorded in a list. If
122     *  the two internal lists for the two input signals both have data, then
123     *  outputs are sent to the output port, which are the difference between
124     *  the time stamps in the two lists.
125     *
126     *  @exception IllegalActionException If thrown when trying to consume input
127     *  events or produce output events.
128     */
129    @Override
130    public void fire() throws IllegalActionException {
131        super.fire();
132
133        Double currentTime = ((DEDirector) getDirector()).getModelTime()
134                .getDoubleValue();
135
136        while (input1.hasNewToken(0)) {
137            input1.get(0);
138            _input1TimeStamps.add(currentTime);
139        }
140
141        while (input2.hasNewToken(0)) {
142            input2.get(0);
143            _input2TimeStamps.add(currentTime);
144        }
145
146        boolean nonnegative = ((BooleanToken) this.nonnegative.getToken())
147                .booleanValue();
148        Iterator<Double> input1Iterator = _input1TimeStamps.iterator();
149        Iterator<Double> input2Iterator = _input2TimeStamps.iterator();
150        while (input1Iterator.hasNext() && input2Iterator.hasNext()) {
151            double input1 = input1Iterator.next();
152            double input2 = input2Iterator.next();
153            input2Iterator.remove();
154            double difference = input2 - input1;
155
156            if (nonnegative) {
157                while (difference < 0.0 && input2Iterator.hasNext()) {
158                    input2 = input2Iterator.next();
159                    input2Iterator.remove();
160                    difference = input2 - input1;
161                }
162                if (difference >= 0.0) {
163                    input1Iterator.remove();
164                    output.send(0, new DoubleToken(difference));
165                }
166            } else {
167                input1Iterator.remove();
168                output.send(0, new DoubleToken(difference));
169            }
170        }
171    }
172
173    /** Initialize this actor.
174     *
175     *  @exception IllegalActionException Never thrown.
176     */
177    @Override
178    public void initialize() throws IllegalActionException {
179        super.initialize();
180        _input1TimeStamps.clear();
181        _input2TimeStamps.clear();
182    }
183
184    /** Return ture if this actor can fire. This actor can fire if prefire() of
185     *  the superclass returns true, and either of the two input ports, or both,
186     *  have token.
187     *
188     *  @return true if this actor can fire.
189     *  @exception IllegalActionException If thrown when trying to decide
190     *  whether the input ports have token or not.
191     */
192    @Override
193    public boolean prefire() throws IllegalActionException {
194        return super.prefire() && (input1.hasToken(0) || input2.hasToken(0));
195    }
196
197    /** The first input port. */
198    public TypedIOPort input1;
199
200    /** The second input port. */
201    public TypedIOPort input2;
202
203    /** A boolean parameter to decide whether inputs at input2 should be ignored
204    if they lead to negative outputs. */
205    public Parameter nonnegative;
206
207    /** The output port to which difference values are sent. */
208    public TypedIOPort output;
209
210    /** The list to store the time stamps received at input1 but have never been
211    compared. */
212    private List<Double> _input1TimeStamps = new LinkedList<Double>();
213
214    /** The list to store the time stamps received at input2 but have never been
215    compared. */
216    private List<Double> _input2TimeStamps = new LinkedList<Double>();
217}