001/* An actor that finds the index of the first item in an array to 002 cross a specified threshold. 003 004 Copyright (c) 2003-2014 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 */ 028package ptolemy.actor.lib; 029 030import ptolemy.actor.TypedAtomicActor; 031import ptolemy.actor.TypedIOPort; 032import ptolemy.actor.parameters.PortParameter; 033import ptolemy.data.ArrayToken; 034import ptolemy.data.BooleanToken; 035import ptolemy.data.DoubleToken; 036import ptolemy.data.IntToken; 037import ptolemy.data.expr.Parameter; 038import ptolemy.data.expr.SingletonParameter; 039import ptolemy.data.expr.StringParameter; 040import ptolemy.data.type.ArrayType; 041import ptolemy.data.type.BaseType; 042import ptolemy.kernel.CompositeEntity; 043import ptolemy.kernel.util.IllegalActionException; 044import ptolemy.kernel.util.NameDuplicationException; 045import ptolemy.kernel.util.StringAttribute; 046 047/////////////////////////////////////////////////////////////////// 048//// ArrayLevelCrossing 049 050/** 051 052 <p>Search an array from the specified starting index and report the 053 index of the first item in the array that is below or above the 054 specified threshold. If there is no such item, then -1 is 055 returned. The threshold can be absolute or relative to the value 056 at the starting index. If it is relative, it can be given on a 057 linear scale or in decibels. If the threshold is relative and we 058 are looking for values above the threshold, then values that are 059 above the value at the starting index by more than the threshold 060 are reported. If the threshold is relative and we are looking for 061 values below the threshold, then values that are below the value at 062 the starting index by more than the threshold are reported.</p> 063 064 <p> This actor is a generalization of Matlab code developed by John 065 Signorotti of Southwest Research Institute. The original function 066 was called UFDipSearch.</p> 067 068 @author Edward A. Lee, Steve Neuendorffer 069 @version $Id$ 070 @since Ptolemy II 4.0 071 @Pt.ProposedRating Yellow (eal) 072 @Pt.AcceptedRating Red (cxh) 073 */ 074public class ArrayLevelCrossing extends TypedAtomicActor { 075 /** Construct an actor with the given container and name. 076 * @param container The container. 077 * @param name The name of this actor. 078 * @exception IllegalActionException If the actor cannot be contained 079 * by the proposed container. 080 * @exception NameDuplicationException If the container already has an 081 * actor with this name. 082 */ 083 public ArrayLevelCrossing(CompositeEntity container, String name) 084 throws NameDuplicationException, IllegalActionException { 085 super(container, name); 086 087 start = new PortParameter(this, "start"); 088 start.setExpression("0"); 089 start.setTypeEquals(BaseType.INT); 090 new SingletonParameter(start.getPort(), "_showName") 091 .setToken(BooleanToken.TRUE); 092 new StringAttribute(start.getPort(), "_cardinal") 093 .setExpression("SOUTH"); 094 095 forwards = new Parameter(this, "forwards"); 096 forwards.setExpression("true"); 097 forwards.setTypeEquals(BaseType.BOOLEAN); 098 099 threshold = new PortParameter(this, "threshold"); 100 threshold.setExpression("0.0"); 101 threshold.setTypeEquals(BaseType.DOUBLE); 102 new StringAttribute(threshold.getPort(), "_cardinal") 103 .setExpression("SOUTH"); 104 new Parameter(threshold.getPort(), "_showName").setExpression("true"); 105 106 above = new Parameter(this, "above"); 107 above.setExpression("false"); 108 above.setTypeEquals(BaseType.BOOLEAN); 109 110 scale = new StringParameter(this, "scale"); 111 scale.setExpression("absolute"); 112 scale.addChoice("absolute"); 113 scale.addChoice("relative linear"); 114 scale.addChoice("relative amplitude decibels"); 115 scale.addChoice("relative power decibels"); 116 117 // Ports 118 array = new TypedIOPort(this, "array", true, false); 119 output = new TypedIOPort(this, "output", false, true); 120 121 // Set Type Constraints. 122 array.setTypeEquals(new ArrayType(BaseType.DOUBLE)); 123 output.setTypeEquals(BaseType.INT); 124 } 125 126 /////////////////////////////////////////////////////////////////// 127 //// ports and parameters //// 128 129 /** An indicator of whether to look for values above or below the 130 * specified threshold. This is a boolean that defaults to false, 131 * which specifies to find values below the threshold. 132 */ 133 public Parameter above; 134 135 /** The array to search for a threshold crossing. 136 * This has type {double}. 137 */ 138 public TypedIOPort array; 139 140 /** The direction to search from the start. If true, search forwards. 141 * Otherwise, search backwards. This is a boolean that defaults to true. 142 */ 143 public Parameter forwards; 144 145 /** The output port producing the index of the first bin to break 146 * the threshold. This has type int. 147 */ 148 public TypedIOPort output; 149 150 /** An indicator of whether <i>threshold</i> should be interpreted 151 * as absolute or relative, and if relative, then on a linear 152 * scale, in amplitude decibels, or power decibels. If decibels 153 * are used, then the corresponding linear threshold is 154 * 10^(<i>threshold</i>/<i>N</i>), where <i>N</i> is 20 (for 155 * amplitude decibels) or 10 (for power decibels). 156 * This parameter is a string with possible values "absolute", 157 * "relative linear", "relative amplitude decibels" or "relative 158 * power decibels". The default value is "absolute". 159 */ 160 public StringParameter scale; 161 162 /** The index from which to start looking for a threshold crossing. 163 * This is an integer that defaults to 0. 164 */ 165 public PortParameter start; 166 167 /** The threshold to look for. This is a double that can be 168 * interpreted on an absolute or relative scale, and if relative, 169 * on a linear or decibel scale, depending on the <i>scale</i> 170 * parameter. It defaults to 0.0. 171 */ 172 public PortParameter threshold; 173 174 /////////////////////////////////////////////////////////////////// 175 //// public methods //// 176 177 /** Consume at most one array from the input ports and produce 178 * the index of the first bin that breaks the threshold. 179 * @exception IllegalActionException If there is no director. 180 */ 181 @Override 182 public void fire() throws IllegalActionException { 183 super.fire(); 184 start.update(); 185 threshold.update(); 186 187 if (array.hasToken(0)) { 188 ArrayToken inputArray = (ArrayToken) array.get(0); 189 int inputSize = inputArray.length(); 190 191 int startValue = ((IntToken) start.getToken()).intValue(); 192 193 if (startValue >= inputSize || startValue < 0) { 194 throw new IllegalActionException(this, 195 "start is out of range: " + startValue); 196 } 197 198 int increment = -1; 199 200 if (((BooleanToken) forwards.getToken()).booleanValue()) { 201 increment = 1; 202 } 203 204 double reference = ((DoubleToken) inputArray.getElement(startValue)) 205 .doubleValue(); 206 207 double thresholdValue = ((DoubleToken) threshold.getToken()) 208 .doubleValue(); 209 210 String scaleValue = scale.stringValue(); 211 212 boolean aboveValue = ((BooleanToken) above.getToken()) 213 .booleanValue(); 214 215 if (scaleValue.equals("relative amplitude decibels")) { 216 if (aboveValue) { 217 thresholdValue = reference 218 * Math.pow(10.0, thresholdValue / 20); 219 } else { 220 thresholdValue = reference 221 * Math.pow(10.0, -thresholdValue / 20); 222 } 223 } else if (scaleValue.equals("relative power decibels")) { 224 if (aboveValue) { 225 thresholdValue = reference 226 * Math.pow(10.0, thresholdValue / 10); 227 } else { 228 thresholdValue = reference 229 * Math.pow(10.0, -thresholdValue / 10); 230 } 231 } else if (scaleValue.equals("relative linear")) { 232 if (aboveValue) { 233 thresholdValue = reference + thresholdValue; 234 } else { 235 thresholdValue = reference - thresholdValue; 236 } 237 } 238 239 // Default output if we don't find a crossing. 240 int bin = -1; 241 242 for (int i = startValue; i < inputSize && i >= 0; i += increment) { 243 double currentValue = ((DoubleToken) inputArray.getElement(i)) 244 .doubleValue(); 245 246 if (aboveValue) { 247 // Searching for values above the threshold. 248 if (currentValue > thresholdValue) { 249 bin = i; 250 break; 251 } 252 } else { 253 // Searching for values below the threshold. 254 if (currentValue < thresholdValue) { 255 bin = i; 256 break; 257 } 258 } 259 } 260 261 output.send(0, new IntToken(bin)); 262 } 263 } 264}