001/* A type polymorphic LMS adaptive filter. 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.data.ArrayToken; 032import ptolemy.data.IntToken; 033import ptolemy.data.Token; 034import ptolemy.data.expr.Parameter; 035import ptolemy.data.type.ArrayType; 036import ptolemy.data.type.BaseType; 037import ptolemy.kernel.CompositeEntity; 038import ptolemy.kernel.util.Attribute; 039import ptolemy.kernel.util.IllegalActionException; 040import ptolemy.kernel.util.NameDuplicationException; 041import ptolemy.kernel.util.Settable; 042import ptolemy.kernel.util.StringAttribute; 043import ptolemy.kernel.util.Workspace; 044 045/////////////////////////////////////////////////////////////////// 046//// LMSAdaptive 047 048/** 049 An adaptive filter using the Least-Mean Square (LMS) algorithm, also 050 known as the stochastic gradient algorithm. 051 The initial filter coefficients are given by the <i>initialTaps</i> 052 parameter. The tap values can be observed on the 053 <i>tapValues</i> output. The default initial taps <i>initialTaps</i> 054 are {1, 0, 0, 0}. This actor supports decimation, but not interpolation. 055 <p> 056 When used correctly, this LMS adaptive filter will adapt to try to minimize 057 the mean-squared error of the signal at its <i>error</i> input. 058 In order for this to be possible, the output of the filter should 059 be compared (subtracted from) some reference signal to produce 060 an error signal. 061 That error signal should be fed back to the <i>error</i> input. 062 <p> 063 The <i>stepSize</i> parameter determines the rate of adaptation. 064 If its magnitude is too large, or if it has the wrong sign, then 065 the adaptation algorithm will be unstable. 066 <p> 067 The <i>errorDelay</i> parameter must equal the total number of delays 068 in the path from the output of the filter back to the error input. 069 This ensures correct alignment of the adaptation algorithm. 070 The number of delays must be greater than zero. 071 <p> 072 This actor is type polymorphic, supporting any data type that 073 supports multiplication by a scalar (the <i>stepSize</i>) and 074 addition. 075 <p> 076 The algorithm is simple. Prior to each invocation of the parent 077 class (an FIR filter), which computes the output given the input, 078 this actor updates the coefficients according to the following 079 formula, 080 <pre> 081 newTapValue = oldTapValue + error * stepSize * tapData 082 </pre> 083 where <i>tapData</i> is the contents of the delay line at 084 the tap in question. 085 This assumes that the <i>decimation</i> parameter is set 086 to 1 (the default). If it has a value different from 1, 087 the algorithm is slightly more involved. Similarly, this 088 assumes that the <i>errorDelay</i> is 1. 089 090 @author Edward A. Lee 091 @version $Id$ 092 @since Ptolemy II 1.0 093 @Pt.ProposedRating Yellow (eal) 094 @Pt.AcceptedRating Red (eal) 095 */ 096public class LMSAdaptive extends FIR { 097 /** Construct an actor with the given container and name. 098 * @param container The container. 099 * @param name The name of this actor. 100 * @exception IllegalActionException If the actor cannot be contained 101 * by the proposed container. 102 * @exception NameDuplicationException If the container already has an 103 * actor with this name. 104 */ 105 public LMSAdaptive(CompositeEntity container, String name) 106 throws NameDuplicationException, IllegalActionException { 107 super(container, name); 108 109 interpolation.setVisibility(Settable.NONE); 110 taps.setVisibility(Settable.NONE); 111 112 error = new TypedIOPort(this, "error", true, false); 113 tapValues = new TypedIOPort(this, "tapValues", false, true); 114 115 stepSize = new Parameter(this, "stepSize"); 116 stepSize.setExpression("0.01"); 117 118 errorDelay = new Parameter(this, "errorDelay"); 119 errorDelay.setExpression("1"); 120 errorDelay.setTypeEquals(BaseType.INT); 121 122 // NOTE: This parameter is really just a renaming of the 123 // taps parameter of the base class. Setting it will just 124 // cause the base class to be set. 125 initialTaps = new Parameter(this, "initialTaps"); 126 initialTaps.setTypeAtLeast(ArrayType.ARRAY_BOTTOM); 127 initialTaps.setExpression("{1.0, 0.0, 0.0, 0.0}"); 128 129 // set type constraints. 130 error.setTypeSameAs(input); 131 stepSize.setTypeSameAs(input); 132 tapValues.setTypeSameAs(taps); 133 taps.setTypeAtLeast(initialTaps); 134 135 new StringAttribute(error, "_cardinal").setExpression("SOUTH"); 136 } 137 138 /////////////////////////////////////////////////////////////////// 139 //// ports and parameters //// 140 141 /** The error input port. The type of this port must match that 142 * of the input port. 143 */ 144 public TypedIOPort error; 145 146 /** The number of samples of delay in the feedback loop that 147 * brings the error back. This has a type integer, and 148 * defaults to 1. 149 */ 150 public Parameter errorDelay; 151 152 /** The initial taps of the filter. This has a type of ArrayToken. 153 * By default, it contains the array {1.0, 0.0, 0.0, 0.0}, 154 * meaning that the output of the filter is initially 155 * the same as the input, and that the adaptive filter has 156 * four taps. 157 */ 158 public Parameter initialTaps; 159 160 /** The adaptation step size. This must have a type that can 161 * be multiplied by the input. It defaults to 0.01, a double. 162 */ 163 public Parameter stepSize; 164 165 /** The output of tap values. This has the same type as the 166 * initialTaps. 167 */ 168 public TypedIOPort tapValues; 169 170 /////////////////////////////////////////////////////////////////// 171 //// public methods //// 172 173 /** Override the base class to set the <i>taps</i> parameter if the 174 * <i>initialTaps</i> parameter is changed. 175 * that are used in execution on the next invocation of fire(). 176 * @param attribute The attribute that changed. 177 * @exception IllegalActionException If the attribute contains 178 * an invalid value or if the super method throws it. 179 */ 180 @Override 181 public void attributeChanged(Attribute attribute) 182 throws IllegalActionException { 183 if (attribute == initialTaps) { 184 taps.setToken(initialTaps.getToken()); 185 } else { 186 super.attributeChanged(attribute); 187 } 188 } 189 190 /** Clone the actor into the specified workspace. This calls the 191 * base class and then resets the type constraints. 192 * @param workspace The workspace for the new object. 193 * @return A new actor. 194 * @exception CloneNotSupportedException If a derived class contains 195 * an attribute that cannot be cloned. 196 */ 197 @Override 198 public Object clone(Workspace workspace) throws CloneNotSupportedException { 199 LMSAdaptive newObject = (LMSAdaptive) super.clone(workspace); 200 201 // set the type constraints 202 newObject.initialTaps.setTypeAtLeast(ArrayType.ARRAY_BOTTOM); 203 newObject.error.setTypeSameAs(newObject.input); 204 newObject.tapValues.setTypeSameAs(newObject.taps); 205 newObject.stepSize.setTypeSameAs(newObject.input); 206 newObject.taps.setTypeAtLeast(newObject.initialTaps); 207 return newObject; 208 } 209 210 // FIXME: State update should occur in postfire. 211 212 /** Consume the inputs, update the taps, and produce the outputs. 213 * @exception IllegalActionException If parameter values are invalid, 214 * or if there is no director, or if runtime type conflicts occur. 215 */ 216 @Override 217 public void fire() throws IllegalActionException { 218 // First update the taps 219 int errorDelayValue = ((IntToken) errorDelay.getToken()).intValue(); 220 int decimationValue = ((IntToken) decimation.getToken()).intValue(); 221 int decimationPhaseValue = ((IntToken) decimationPhase.getToken()) 222 .intValue(); 223 int index = errorDelayValue * decimationValue + decimationPhaseValue; 224 Token factor = error.get(0).multiply(stepSize.getToken()); 225 226 for (int i = 0; i < _taps.length; i++) { 227 // The data item to use here should be "index" in the past, 228 // where an index of zero would be the current input. 229 Token datum = _data[(_mostRecent + index - 1) % _data.length]; 230 _taps[i] = _taps[i].add(factor.multiply(datum)); 231 index++; 232 } 233 234 // Update the tapValues output. 235 // NOTE: This may be a relatively costly operation to be doing here. 236 tapValues.send(0, new ArrayToken(_taps)); 237 238 // Then run FIR filter 239 super.fire(); 240 } 241 242 /** Return false if the error input does not have enough tokens to fire. 243 * Otherwise, return what the superclass returns. 244 * @return False if the number of input tokens available is not at least 245 * equal to the decimation parameter. 246 * @exception IllegalActionException If the superclass throws it. 247 */ 248 @Override 249 public boolean prefire() throws IllegalActionException { 250 if (error.hasToken(0)) { 251 return super.prefire(); 252 } else { 253 if (_debugging) { 254 _debug("Called prefire(), which returns false."); 255 } 256 257 return false; 258 } 259 } 260 261 /** Override the base class to re-initialize the taps. 262 * @exception IllegalActionException If the superclass throws it. 263 */ 264 @Override 265 public void initialize() throws IllegalActionException { 266 super.initialize(); 267 // Explicit call of _initializeTabs necessary, since this actor changes 268 // _taps while running. 269 _initializeTaps(); 270 } 271}