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}