001/* A polymorphic autocorrelation function.
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.sdf.lib;
029
030import ptolemy.actor.TypedIOPort;
031import ptolemy.actor.util.ConstVariableModelAnalysis;
032import ptolemy.data.ArrayToken;
033import ptolemy.data.BooleanToken;
034import ptolemy.data.ComplexToken;
035import ptolemy.data.IntToken;
036import ptolemy.data.Token;
037import ptolemy.data.expr.Parameter;
038import ptolemy.data.type.ArrayType;
039import ptolemy.data.type.BaseType;
040import ptolemy.data.type.MonotonicFunction;
041import ptolemy.data.type.Type;
042import ptolemy.graph.InequalityTerm;
043import ptolemy.kernel.CompositeEntity;
044import ptolemy.kernel.util.Attribute;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.NameDuplicationException;
047import ptolemy.kernel.util.Workspace;
048
049///////////////////////////////////////////////////////////////////
050//// Autocorrelation
051
052/**
053 This actor calculates the autocorrelation of a sequence of input tokens.
054 <a name="autocorrelation"></a>
055 It is polymorphic, supporting any input data type that supports
056 multiplication, addition, and division by an integer.
057 However, since integer division will lose the fractional portion of the
058 result, type resolution will resolve the input type to double or double
059 matrix if the input port is connected to an integer or integer matrix source,
060 respectively.
061 <p>
062 Both biased and unbiased autocorrelation estimates are supported.
063 If the parameter <i>biased</i> is true, then
064 the autocorrelation estimate is
065 <a name="unbiasedAutocorrelation"></a>
066 <pre>
067          N-1-k
068        1  ---
069 r(k) = -  \    x(n)x(n+k)
070        N  /
071           ---
072           n=0
073 </pre>
074 for <i>k </i>= 0<i>, ... , p</i>, where <i>N</i> is the number of
075 inputs to average (<i>numberOfInputs</i>), <i>p</i> is the number of
076 lags to estimate (<i>numberOfLags</i>), and x<sup>*</sup> is the
077 conjugate of the input (if it is complex).
078 This estimate is biased because the outermost lags have fewer than <i>N</i>
079 <a name="biasedAutocorrelation"></a>
080 terms in the summation, and yet the summation is still normalized by <i>N</i>.
081 <p>
082 If the parameter <i>biased</i> is false (the default), then the estimate is
083 <pre>
084            N-1-k
085         1   ---
086 r(k) = ---  \    x(n)x(n+k)
087        N-k  /
088             ---
089             n=0
090 </pre>
091 In this case, the estimate is unbiased.
092 However, note that the unbiased estimate does not guarantee
093 a positive definite sequence, so a power spectral estimate based on this
094 autocorrelation estimate may have negative components.
095 <a name="spectralEstimation"></a>
096 <p>
097 The output will be an array of tokens whose type is at least that
098 of the input. If the parameter <i>symmetricOutput</i> is true,
099 then the output will be symmetric and have length equal to twice
100 the number of lags requested plus one.  Otherwise, the output
101 will have length equal to twice the number of lags requested,
102 which will be almost symmetric (insert the last
103 sample into the first position to get the symmetric output that you
104 would get with the <i>symmetricOutput</i> being true).
105
106 @author Edward A. Lee and Yuhong Xiong
107 @version $Id$
108 @since Ptolemy II 1.0
109 @Pt.ProposedRating Green (eal)
110 @Pt.AcceptedRating Yellow (neuendor)
111 */
112public class Autocorrelation extends SDFTransformer {
113    /** Construct an actor with the given container and name.
114     *  @param container The container.
115     *  @param name The name of this actor.
116     *  @exception IllegalActionException If the actor cannot be contained
117     *   by the proposed container.
118     *  @exception NameDuplicationException If the container already has an
119     *   actor with this name.
120     */
121    public Autocorrelation(CompositeEntity container, String name)
122            throws NameDuplicationException, IllegalActionException {
123        super(container, name);
124
125        input_tokenConsumptionRate.setExpression("numberOfInputs");
126
127        numberOfInputs = new Parameter(this, "numberOfInputs",
128                new IntToken(256));
129        numberOfInputs.setTypeEquals(BaseType.INT);
130
131        numberOfLags = new Parameter(this, "numberOfLags", new IntToken(64));
132        numberOfLags.setTypeEquals(BaseType.INT);
133
134        biased = new Parameter(this, "biased", new BooleanToken(false));
135        biased.setTypeEquals(BaseType.BOOLEAN);
136
137        symmetricOutput = new Parameter(this, "symmetricOutput",
138                new BooleanToken(false));
139        symmetricOutput.setTypeEquals(BaseType.BOOLEAN);
140
141        input.setTypeAtLeast(new FunctionTerm(input));
142
143        // Set the output type to be an ArrayType.
144        output.setTypeAtLeast(new OutputTypeTerm());
145
146        attributeChanged(numberOfInputs);
147    }
148
149    ///////////////////////////////////////////////////////////////////
150    ////                         parameters                        ////
151
152    /** If true, the estimate will be biased.
153     *  This is a boolean with default value false.
154     */
155    public Parameter biased;
156
157    /** Number of input samples to average.
158     *  This is an integer with default value 256.
159     */
160    public Parameter numberOfInputs;
161
162    /** Number of autocorrelation lags to output.
163     *  This is an integer with default value 64.
164     */
165    public Parameter numberOfLags;
166
167    /** If true, then the output from each firing
168     *  will have 2*<i>numberOfLags</i> + 1
169     *  samples (an odd number) whose values are symmetric about
170     *  the midpoint. If false, then the output from each firing will
171     *  have 2*<i>numberOfLags</i> samples (an even number)
172     *  by omitting one of the endpoints (the last one).
173     *  This is a boolean with default value false.
174     */
175    public Parameter symmetricOutput;
176
177    ///////////////////////////////////////////////////////////////////
178    ////                         public methods                    ////
179
180    /** Check to see that the numberOfInputs parameter is positive,
181     *  and that the numberOfLags parameter is positive.  Based on the
182     *  new values, recompute the size of the output array.
183     *  @param attribute The attribute that has changed.
184     *  @exception IllegalActionException If the parameters are out of range.
185     */
186    @Override
187    public void attributeChanged(Attribute attribute)
188            throws IllegalActionException {
189        if (attribute == numberOfInputs || attribute == numberOfLags
190                || attribute == symmetricOutput) {
191            _numberOfInputs = ((IntToken) numberOfInputs.getToken()).intValue();
192            _numberOfLags = ((IntToken) numberOfLags.getToken()).intValue();
193            _symmetricOutput = ((BooleanToken) symmetricOutput.getToken())
194                    .booleanValue();
195
196            if (_numberOfInputs <= 0) {
197                throw new IllegalActionException(this,
198                        "Invalid numberOfInputs: " + _numberOfInputs);
199            }
200
201            if (_numberOfLags <= 0) {
202                throw new IllegalActionException(this,
203                        "Invalid numberOfLags: " + _numberOfLags);
204            }
205
206            if (_symmetricOutput) {
207                _lengthOfOutput = 2 * _numberOfLags + 1;
208            } else {
209                _lengthOfOutput = 2 * _numberOfLags;
210            }
211
212            if (_outputs == null || _lengthOfOutput != _outputs.length) {
213                _outputs = new Token[_lengthOfOutput];
214            }
215        } else {
216            super.attributeChanged(attribute);
217        }
218    }
219
220    /** Clone the actor into the specified workspace. This calls the
221     *  base class and then sets the type constraints.
222     *  @param workspace The workspace for the new object.
223     *  @return A new actor.
224     *  @exception CloneNotSupportedException If a derived class has
225     *   an attribute that cannot be cloned.
226     */
227    @Override
228    public Object clone(Workspace workspace) throws CloneNotSupportedException {
229        Autocorrelation newObject = (Autocorrelation) super.clone(workspace);
230        newObject.input.setTypeAtLeast(new FunctionTerm(newObject.input));
231        newObject.output.setTypeAtLeast(newObject.new OutputTypeTerm());
232
233        newObject._outputs = new Token[newObject._lengthOfOutput];
234        System.arraycopy(_outputs, 0, newObject._outputs, 0, _outputs.length);
235
236        return newObject;
237    }
238
239    /** Consume tokens from the input and produce a token on the output
240     *  that contains an array token that represents an autocorrelation
241     *  estimate of the consumed tokens.  The estimate is consistent with
242     *  the parameters of the object, as described in the class comment.
243     *  @exception IllegalActionException If there is no director.
244     */
245    @Override
246    public void fire() throws IllegalActionException {
247        super.fire();
248
249        boolean biasedValue = ((BooleanToken) biased.getToken()).booleanValue();
250        Token[] inputValues = input.get(0, _numberOfInputs);
251        int notSymmetric = _symmetricOutput ? 0 : 1;
252
253        // NOTE: Is there a better way to determine whether the input
254        // is complex?
255        boolean complex = inputValues[0] instanceof ComplexToken;
256
257        for (int i = _numberOfLags; i >= 0; i--) {
258            Token sum = inputValues[0].zero();
259
260            for (int j = 0; j < _numberOfInputs - i; j++) {
261                if (complex) {
262                    ComplexToken conjugate = new ComplexToken(
263                            ((ComplexToken) inputValues[j]).complexValue()
264                                    .conjugate());
265                    sum = sum.add(conjugate.multiply(inputValues[j + i]));
266                } else {
267                    sum = sum.add(inputValues[j].multiply(inputValues[j + i]));
268                }
269            }
270
271            if (biasedValue) {
272                _outputs[i + _numberOfLags - notSymmetric] = sum
273                        .divide(numberOfInputs.getToken());
274            } else {
275                _outputs[i + _numberOfLags - notSymmetric] = sum
276                        .divide(new IntToken(_numberOfInputs - i));
277            }
278        }
279
280        // Now fill in the first half, which by symmetry is just
281        // identical to what was just produced, or its conjugate if
282        // the input is complex.
283        for (int i = _numberOfLags - 1 - notSymmetric; i >= 0; i--) {
284            if (complex) {
285                ComplexToken candidate = (ComplexToken) _outputs[2
286                        * (_numberOfLags - notSymmetric) - i];
287                _outputs[i] = new ComplexToken(
288                        candidate.complexValue().conjugate());
289            } else {
290                _outputs[i] = _outputs[2 * (_numberOfLags - notSymmetric) - i];
291            }
292        }
293
294        output.broadcast(new ArrayToken(_outputs));
295    }
296
297    /** If there are not sufficient inputs, then return false.
298     *  Otherwise, return whatever the base class returns.
299     *  @exception IllegalActionException If the base class throws it.
300     *  @return True if it is ok to continue.
301     */
302    @Override
303    public boolean prefire() throws IllegalActionException {
304        if (!input.hasToken(0, _numberOfInputs)) {
305            if (_debugging) {
306                _debug("Called prefire(), which returns false.");
307            }
308
309            return false;
310        } else {
311            return super.prefire();
312        }
313    }
314
315    ///////////////////////////////////////////////////////////////////
316    ////                         private variables                 ////
317    private int _numberOfInputs;
318
319    private int _numberOfLags;
320
321    private int _lengthOfOutput;
322
323    private boolean _symmetricOutput;
324
325    private Token[] _outputs;
326
327    ///////////////////////////////////////////////////////////////////
328    ////                         inner classes                     ////
329    // This class implements a monotonic function of the input port
330    // type. The result of the function is the same as the input type
331    // if is not Int or IntMatrix; otherwise, the result is Double
332    // or DoubleMatrix, respectively.
333    private static class FunctionTerm extends MonotonicFunction {
334        // The constructor takes a port argument so that the clone()
335        // method can construct an instance of this class for the
336        // input port on the clone.
337        private FunctionTerm(TypedIOPort port) {
338            _port = port;
339        }
340
341        ///////////////////////////////////////////////////////////////
342        ////                       public inner methods            ////
343
344        /** Return the function result.
345         *  @return A Type.
346         */
347        @Override
348        public Object getValue() {
349            Type inputType = _port.getType();
350
351            if (inputType == BaseType.INT) {
352                return BaseType.DOUBLE;
353            } else if (inputType == BaseType.INT_MATRIX) {
354                return BaseType.DOUBLE_MATRIX;
355            } else {
356                return inputType;
357            }
358        }
359
360        /** Return an one element array containing the InequalityTerm
361         *  representing the type of the input port.
362         *  @return An array of InequalityTerm.
363         */
364        @Override
365        public InequalityTerm[] getVariables() {
366            InequalityTerm[] variable = new InequalityTerm[1];
367            variable[0] = _port.getTypeTerm();
368            return variable;
369        }
370
371        ///////////////////////////////////////////////////////////////
372        ////                       private inner variable          ////
373        private TypedIOPort _port;
374    }
375
376    /** Monotonic function that determines the type of the output port.
377     */
378    private class OutputTypeTerm extends MonotonicFunction {
379
380        ///////////////////////////////////////////////////////////////
381        ////                   public inner methods                ////
382
383        /** Return an array type with element types given by the
384         *  associated typeable.
385         *  @return An ArrayType.
386         *  @exception IllegalActionException If the type of the
387         *  associated typeable cannot be determined.
388         */
389        @Override
390        public Object getValue() throws IllegalActionException {
391            ConstVariableModelAnalysis analysis = ConstVariableModelAnalysis
392                    .getAnalysis(symmetricOutput);
393            if (analysis.isConstant(symmetricOutput)
394                    && analysis.isConstant(numberOfLags)) {
395                Token symmetricOutputToken = analysis
396                        .getConstantValue(symmetricOutput);
397                Token numberOfLagsToken = analysis
398                        .getConstantValue(numberOfLags);
399                int lags = ((IntToken) numberOfLagsToken).intValue();
400                if (((BooleanToken) symmetricOutputToken).booleanValue()) {
401                    return _getArrayTypeRaw(2 * lags + 1);
402                } else {
403                    return _getArrayTypeRaw(2 * lags);
404                }
405            }
406            return _getArrayTypeRaw();
407        }
408
409        /** Return an array containing the type term for the actor's
410         *  input port.
411         *  @return An array of InequalityTerm.
412         */
413        @Override
414        public InequalityTerm[] getVariables() {
415            InequalityTerm[] array = new InequalityTerm[1];
416            array[0] = input.getTypeTerm();
417            return array;
418        }
419
420        ///////////////////////////////////////////////////////////////
421        ////                   private methods                     ////
422
423        /** Get an array type with element type matching the type
424         *  of the associated typeable.
425         *  @return An array type for the associated typeable.
426         *  @exception IllegalActionException If the type of the typeable
427         *   cannot be determined.
428         */
429        private ArrayType _getArrayTypeRaw() throws IllegalActionException {
430            Type type = input.getType();
431            if (_arrayType == null
432                    || !_arrayType.getElementType().equals(type)) {
433                _arrayType = new ArrayType(type);
434            }
435            return _arrayType;
436        }
437
438        /** Get an array type with element type matching the type
439         *  of the associated typeable.
440         *  @return An array type for the associated typeable.
441         *  @exception IllegalActionException If the type of the typeable
442         *   cannot be determined.
443         */
444        private ArrayType _getArrayTypeRaw(int length)
445                throws IllegalActionException {
446            Type type = input.getType();
447            if (_arrayType == null
448                    || !_arrayType.getElementType().equals(type)) {
449                _arrayType = new ArrayType(type, length);
450            }
451            return _arrayType;
452        }
453
454        ///////////////////////////////////////////////////////////////
455        ////                   private members                     ////
456
457        /** The array type with element types matching the typeable. */
458        private ArrayType _arrayType;
459    }
460}