001/* A calendar queue implementation of the DE event queue.
002
003 Copyright (c) 1998-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.de.kernel;
029
030import ptolemy.actor.util.CQComparator;
031import ptolemy.actor.util.CalendarQueue;
032import ptolemy.actor.util.Time;
033import ptolemy.kernel.util.DebugListener;
034import ptolemy.kernel.util.InvalidStateException;
035
036///////////////////////////////////////////////////////////////////
037//// DECQEventQueue
038
039/**
040 A calendar queue implementation of the DE event queue.
041 This queue stores DE events in the order of their timestamps,
042 microsteps, and then depths of their destination actors. See
043 {@link DEEventQueue} for more explanation of the order of DE events.
044 <P>
045 Its complexity is theoretically O(1) for both enqueue and dequeue
046 operations, assuming a reasonable distribution of timestamps. See
047 {@link ptolemy.actor.util.CalendarQueue}.
048
049 @author Lukito Muliadi, Edward A. Lee, Jie Liu, Haiyang Zheng
050 @version $Id$
051 @since Ptolemy II 0.2
052 @Pt.ProposedRating Green (hyzheng)
053 @Pt.AcceptedRating Green (hyzheng)
054 */
055public class DECQEventQueue implements DEEventQueue {
056    /** Construct an empty event queue.
057     */
058    public DECQEventQueue() {
059        // Construct a calendar queue _cQueue with its default parameters:
060        // minBinCount is 2, binCountFactor is 2, and isAdaptive is true.
061        _cQueue = new CalendarQueue(new DECQComparator());
062    }
063
064    /** Construct an empty event queue with the specified parameters.
065     *  @param minBinCount The minimum number of bins.
066     *  @param binCountFactor The factor when changing the bin count.
067     *  @param isAdaptive If the queue changes its number of bins at run time.
068     */
069    public DECQEventQueue(int minBinCount, int binCountFactor,
070            boolean isAdaptive) {
071        // Construct a calendar queue _cQueue with the given parameters.
072        _cQueue = new CalendarQueue(new DECQComparator(), minBinCount,
073                binCountFactor);
074        _cQueue.setAdaptive(isAdaptive);
075    }
076
077    ///////////////////////////////////////////////////////////////////
078    ////                         public methods                    ////
079
080    /** Append a listener to the current set of debug listeners.
081     *  @param listener A listener to which to send debug messages.
082     *  @see #removeDebugListener(DebugListener)
083     */
084    @Override
085    public void addDebugListener(DebugListener listener) {
086        _cQueue.addDebugListener(listener);
087    }
088
089    /** Empty the event queue. This method is synchronized since there
090     *  may be actors running under different threads in the DE domain.
091     */
092    @Override
093    public void clear() {
094        _cQueue.clear();
095    }
096
097    /** Return the earliest DE event in the queue without removing it
098     *  from the queue.
099     *  @return The earliest DE event in the queue.
100     *  @exception InvalidStateException If the queue is empty.
101     */
102    @Override
103    public final DEEvent get() {
104        return (DEEvent) _cQueue.get();
105    }
106
107    /** Return true if this event queue is empty.
108     *  @return True if there are no event in the queue.
109     */
110    @Override
111    public final boolean isEmpty() {
112        return _cQueue.isEmpty();
113    }
114
115    /** Put an event into the event queue.
116     *  If the given DE event is not in the event queue, enqueue it
117     *  into the event queue and notify all threads
118     *  that are stalled waiting for a DE event to be put in the queue.
119     *  This method is synchronized since there
120     *  may be actors running under different threads in the DE domain.
121     *  @param event The event to enqueue.
122     */
123    @Override
124    public synchronized final void put(DEEvent event) {
125        if (!_cQueue.includes(event)) {
126            _cQueue.put(event);
127            notifyAll();
128        }
129    }
130
131    /** Remove an event from the event queue and return true if
132     *  it was removed, and false if it was not in the queue.
133     *  This should only be used for pure events (consequences of
134     *  fireAt()), not for events carrying payloads, because this
135     *  does not remove the payload from the DEReceiver.
136     *  The event passed is an argument need not be exactly the
137     *  same event in the queue. It just has to match the
138     *  actor, timeStamp, microstep, and depth of the event
139     *  to be removed.
140     *  @param event The event to enqueue.
141     *  @return True If a match is found and the entry is removed.
142     */
143    @Override
144    public synchronized final boolean remove(DEEvent event) {
145        return _cQueue.remove(event);
146    }
147
148    /** Unregister a debug listener.  If the specified listener has not
149     *  been previously registered, then do nothing.
150     *  @param listener The listener to remove from the list of listeners
151     *   to which debug messages are sent.
152     *  @see #addDebugListener(DebugListener)
153     */
154    @Override
155    public void removeDebugListener(DebugListener listener) {
156        _cQueue.removeDebugListener(listener);
157    }
158
159    /** Return the size of the event queue.
160     *  @return The size of the event queue.
161     */
162    @Override
163    public final int size() {
164        return _cQueue.size();
165    }
166
167    /** Dequeue the earliest DE event in this event queue.
168     *  @return The earliest DE event in the queue.
169     *  @exception InvalidStateException If the queue is empty.
170     */
171    @Override
172    public final DEEvent take() {
173        return (DEEvent) _cQueue.take();
174    }
175
176    /** Return the events currently in the queue as an array.
177     *  @return The events currently in the queue.
178     */
179    @Override
180    public final Object[] toArray() {
181        return _cQueue.toArray();
182    }
183
184    /** Describe the Contents of the queue as a string.
185     *  @return A string with a comma-separated list of events.
186     */
187    @Override
188    public String toString() {
189        Object[] array = toArray();
190        StringBuffer buffer = new StringBuffer("");
191        if (array != null) {
192            buffer.append("{");
193            for (int i = 0; i < array.length; i++) {
194                if (i > 0) {
195                    buffer.append(", ");
196                }
197                buffer.append(array[i]);
198            }
199            buffer.append("}");
200        }
201        return buffer.toString();
202    }
203
204    ///////////////////////////////////////////////////////////////////
205    ////                         private inner class               ////
206    // An implementation of the CQComparator interface for use with
207    // calendar queue that compares two DEEvents according to their
208    // time stamps, microstep, and depth in that order.
209    // One DE event is said to be earlier than another, if it has
210    // a smaller time stamp, or when the time stamps are identical,
211    // it has a smaller microstep, or when both time stamps and
212    // microsteps are identical, it has a smaller depth.
213    // The default binWidth is 1.0, and the default zeroReference is 0.0.
214    private static class DECQComparator implements CQComparator {
215
216        // FindBugs suggests making this class static so as to decrease
217        // the size of instances and avoid dangling references.
218
219        /** Construct a new comparator.
220         */
221        public DECQComparator() {
222            _binWidth = 1;
223            _zeroReference = 0.0;
224        }
225
226        /** Compare two arguments for order. Return a negative integer,
227         *  zero, or a positive integer if the first argument is less than,
228         *  equal to, or greater than the second.
229         *  Both arguments must be instances of DEEvent. Otherwise a
230         *  ClassCastException will be thrown.  The compareTo() method
231         *  of the first argument is used to do the comparison.
232         *
233         *  @param object1 The first DE event.
234         *  @param object2 The second DE event.
235         *  @return A negative integer, zero, or a positive integer if the first
236         *   argument is less than, equal to, or greater than the second.
237         *  @exception ClassCastException If any of the arguments is not
238         *   an instance of DEEvent.
239         */
240        @Override
241        public final int compare(Object object1, Object object2) {
242            return ((DEEvent) object1).compareTo((DEEvent) object2);
243        }
244
245        /** Given a DE event, return the virtual index of the bin that
246         *  should contain this event. If the argument is not an instance
247         *  of DEEvent, then a ClassCastException will be thrown.
248         *  If the bin number is larger than what can be represented
249         *  in a long, then the low-order 64 bits will be returned.
250         *  Note that this could change the sign of the result, but
251         *  the way this is used in the CalendarQueue class, this is OK.
252         *  It is converted to a bin number by masking some number of
253         *  low-order bits, so the result will be unaffected by the
254         *  sign error.
255         *  Note that this method cannot return a long.MAX_VALUE, which
256         *  is used internally by CalendarQueue class.
257         *  @param event The event.
258         *  @return The index of the virtual bin containing the event.
259         *  @exception ClassCastException If the argument is not
260         *   an instance of DEEvent.
261         */
262        @Override
263        public final long getVirtualBinNumber(Object event) {
264            // Note: The longValue() method will only
265            // returns the low-order 64 bits of the result.
266            // If it is larger than what can be represented
267            // in 64 bits, then the returned result will be wrapped.
268            long value = (long) (((DEEvent) event).timeStamp()
269                    .subtract(_zeroReference).getLongValue() / _binWidth);
270            if (value != Long.MAX_VALUE) {
271                return value;
272            } else {
273                return Long.MAX_VALUE - 1;
274            }
275        }
276
277        /** Given an array of DE events, set an appropriate bin width.
278         *  This method assumes that no two DE events have the same time stamp,
279         *  microstep, and depth. This method also assumes that the events
280         *  are sorted in a time-increasing order. {@link DEEventQueue}.
281         *  Note that even the time stamps may not be increasing,
282         *  the receiver depths, or the microsteps may be increasing.
283         *  This method attempts to choose the bin width so that
284         *  the average number of entries in a bin is one.
285         *  If the argument is null or is an array with length less
286         *  than two, set the bin width to the default, which is 1.0
287         *  for this implementation.
288         *
289         *  @param entryArray An array of DE events.
290         *  @exception ClassCastException If any entry in the array is not
291         *  an instance of DEEvent.
292         */
293        @Override
294        public void setBinWidth(Object[] entryArray) {
295            if (entryArray == null || entryArray.length < 2) {
296                // Reset to default.
297                _binWidth = 1;
298                _zeroReference = 0.0;
299                return;
300            }
301
302            double[] diff = new double[entryArray.length - 1];
303            Time firstEntryTime = ((DEEvent) entryArray[0]).timeStamp();
304            Time lastEntryTime = ((DEEvent) entryArray[entryArray.length - 1])
305                    .timeStamp();
306
307            if (firstEntryTime.isInfinite()
308                    && firstEntryTime.equals(lastEntryTime)) {
309                // To avoid setting NaN or 0.0
310                // for the width, apparently due to simultaneous events,
311                // we leave it unchanged instead.
312                return;
313            }
314
315            double average = lastEntryTime.subtract(firstEntryTime)
316                    .getDoubleValue();
317            average = average / (entryArray.length - 1);
318
319            double effectiveAverage = 0;
320            int effectiveSamples = 0;
321
322            if (Double.isInfinite(average)) {
323                return;
324            }
325
326            for (int i = 0; i < entryArray.length - 1; ++i) {
327                diff[i] = ((DEEvent) entryArray[i + 1]).timeStamp()
328                        .subtract(((DEEvent) entryArray[i]).timeStamp())
329                        .getDoubleValue();
330                if (diff[i] < 2 * average) {
331                    effectiveSamples++;
332                    effectiveAverage = effectiveAverage + diff[i];
333                }
334            }
335
336            if (effectiveAverage == 0 || effectiveSamples == 0) {
337                // To avoid setting NaN or 0.0
338                // for the width, apparently due to simultaneous events,
339                // we leave it unchanged instead.
340                return;
341            }
342
343            effectiveAverage = effectiveAverage / effectiveSamples;
344            _binWidth = effectiveAverage * 3;
345        }
346
347        /** Set the zero reference, to be used in calculating the virtual
348         *  bin number. The argument should be a DE event, otherwise a
349         *  ClassCastException will be thrown.
350         *  @exception ClassCastException If the argument is not an instance
351         *   of DEEvent.
352         */
353        @Override
354        public void setZeroReference(Object zeroReference) {
355            _zeroReference = ((DEEvent) zeroReference).timeStamp()
356                    .getDoubleValue();
357        }
358
359        ///////////////////////////////////////////////////////////////////
360        ////                         private members                   ////
361        // The bin width.
362        private double _binWidth = 1.0;
363
364        // The zero reference.
365        private double _zeroReference = 0.0;
366    }
367
368    ///////////////////////////////////////////////////////////////////
369    ////                         private variables                 ////
370    // An instance of CalendarQueue used for sorting and storing events.
371    private CalendarQueue _cQueue;
372}