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}