001/* A type polymorphic FIR 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 java.util.HashSet;
031import java.util.Set;
032
033import ptolemy.data.ArrayToken;
034import ptolemy.data.IntToken;
035import ptolemy.data.Token;
036import ptolemy.data.expr.Parameter;
037import ptolemy.data.type.ArrayType;
038import ptolemy.data.type.BaseType;
039import ptolemy.data.type.MonotonicFunction;
040import ptolemy.data.type.Type;
041import ptolemy.graph.Inequality;
042import ptolemy.graph.InequalityTerm;
043import ptolemy.kernel.CompositeEntity;
044import ptolemy.kernel.util.Attribute;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.InternalErrorException;
047import ptolemy.kernel.util.NameDuplicationException;
048import ptolemy.kernel.util.Workspace;
049
050///////////////////////////////////////////////////////////////////
051//// FIR
052
053/**
054 This actor implements a type polymorphic finite-impulse response
055 filter with multirate capability. Since this filter operates on
056 Tokens, it is polymorphic in the type of data it operates on.
057 <p>
058 Note that the current implementation of this actor only reads its
059 parameters during initialization, so the filter cannot be
060 changed during execution.
061 <p>
062 When the <i>decimation</i> (<i>interpolation</i>)
063 parameters are different from unity, the filter behaves exactly
064 as it were followed (preceded) by a DownSample (UpSample) actor.
065 However, the implementation is much more efficient than
066 it would be using UpSample or DownSample actors;
067 a polyphase structure is used internally, avoiding unnecessary use
068 of memory and unnecessary multiplication by zero.
069 Arbitrary sample-rate conversions by rational factors can
070 be accomplished this way.
071 <p>
072 To design a filter for a multirate system, simply assume the
073 sample rate is the product of the interpolation parameter and
074 the input sample rate, or equivalently, the product of the decimation
075 parameter and the output sample rate.
076 In particular, considerable care must be taken to avoid aliasing.
077 Specifically, if the input sample rate is <i>f</i>,
078 then the filter stopband should begin before <i>f</i>/2.
079 If the interpolation ratio is <i>i</i>, then <i>f</i>/2 is a fraction
080 1/2<i>i</i> of the sample rate at which you must design your filter.
081 <p>
082 The <i>decimationPhase</i> parameter is somewhat subtle.
083 It is exactly equivalent the phase parameter of the DownSample actor.
084 Its interpretation is as follows; when decimating,
085 samples are conceptually discarded (although a polyphase structure
086 does not actually compute the discarded samples).
087 If you are decimating by a factor of three, then you will select
088 one of every three outputs, with three possible phases.
089 When decimationPhase is zero (the default),
090 the latest (most recent) samples are the ones selected.
091 The decimationPhase must be strictly less than
092 the decimation ratio.
093 <p>
094 <i>Note: in this description "sample rate" refers to the physical sampling
095 rate of an A/D converter in the system.  In other words, the number of
096 data samples per second.  This is not usually specified anywhere in an
097 SDF system, and most definitely does NOT correspond to the SDF rate parameters
098 of this actor.  This actor automatically sets the rates of the input
099 and output ports to the decimation and interpolation ratios, respectively.</i>
100 <p>
101 For more information about polyphase filters, see F. J. Harris,
102 "Multirate FIR Filters for Interpolating and Desampling", in
103 <i>Handbook of Digital Signal Processing</i>, Academic Press, 1987.
104
105 @author Edward A. Lee, Bart Kienhuis, Steve Neuendorffer
106 @version $Id$
107 @since Ptolemy II 0.2
108 @Pt.ProposedRating Yellow (neuendor)
109 @Pt.AcceptedRating Yellow (neuendor)
110 @see ptolemy.data.Token
111 */
112public class FIR 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 FIR(CompositeEntity container, String name)
122            throws NameDuplicationException, IllegalActionException {
123        super(container, name);
124
125        decimation = new Parameter(this, "decimation");
126        decimation.setExpression("1");
127        decimation.setTypeEquals(BaseType.INT);
128
129        decimationPhase = new Parameter(this, "decimationPhase");
130        decimationPhase.setExpression("0");
131        decimationPhase.setTypeEquals(BaseType.INT);
132
133        interpolation = new Parameter(this, "interpolation");
134        interpolation.setExpression("1");
135        interpolation.setTypeEquals(BaseType.INT);
136
137        taps = new Parameter(this, "taps");
138        taps.setExpression("{1.0}");
139        taps.setTypeAtLeast(ArrayType.ARRAY_BOTTOM);
140
141        input_tokenConsumptionRate.setExpression("decimation");
142        output_tokenProductionRate.setExpression("interpolation");
143
144    }
145
146    ///////////////////////////////////////////////////////////////////
147    ////                         public variables                  ////
148
149    /** The decimation ratio of the filter. This must contain an
150     *  IntToken, and by default it has value one.
151     */
152    public Parameter decimation;
153
154    /** The decimation phase of the filter. This must contain an
155     *  IntToken, and by default it has value zero.
156     */
157    public Parameter decimationPhase;
158
159    /** The interpolation ratio of the filter. This must contain an
160     *  IntToken, and by default it has value one.
161     */
162    public Parameter interpolation;
163
164    /** The taps of the filter. This has a type of ArrayToken.
165     *  By default, it contains an array with a single integer one,
166     *  meaning that the output of the filter is the same as the input.
167     */
168    public Parameter taps;
169
170    ///////////////////////////////////////////////////////////////////
171    ////                         public methods                    ////
172
173    /** Set a flag that causes recalculation of various local variables
174     *  that are used in execution on the next invocation of fire().
175     *  @param attribute The attribute that changed.
176     *  @exception IllegalActionException If the attribute contains
177     *  an invalid value or if the super method throws it.
178     */
179    @Override
180    public void attributeChanged(Attribute attribute)
181            throws IllegalActionException {
182        if (attribute == interpolation) {
183            IntToken token = (IntToken) interpolation.getToken();
184            _interpolationValue = token.intValue();
185
186            if (_interpolationValue <= 0) {
187                throw new IllegalActionException(this, "Invalid interpolation: "
188                        + _interpolationValue + ". Must be positive.");
189            }
190
191            _reinitializeNeeded = true;
192        } else if (attribute == decimation) {
193            IntToken token = (IntToken) decimation.getToken();
194            _decimationValue = token.intValue();
195
196            if (_decimationValue <= 0) {
197                throw new IllegalActionException(this, "Invalid decimation: "
198                        + _decimationValue + ". Must be positive.");
199            }
200
201            _reinitializeNeeded = true;
202        } else if (attribute == decimationPhase) {
203            IntToken token = (IntToken) decimationPhase.getToken();
204            _decimationPhaseValue = token.intValue();
205
206            if (_decimationPhaseValue < 0) {
207                throw new IllegalActionException(this,
208                        "Invalid decimationPhase: " + _decimationPhaseValue
209                                + ". Must be nonnegative.");
210            }
211
212            _reinitializeNeeded = true;
213        } else if (attribute == taps) {
214            _initializeTaps();
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 resets the type constraints.
222     *  @param workspace The workspace for the new object.
223     *  @return A new actor.
224     *  @exception CloneNotSupportedException If a derived class contains
225     *   an attribute that cannot be cloned.
226     */
227    @Override
228    public Object clone(Workspace workspace) throws CloneNotSupportedException {
229        FIR newObject = (FIR) super.clone(workspace);
230
231        // Set the type constraints.
232        newObject.taps.setTypeAtLeast(ArrayType.ARRAY_BOTTOM);
233        newObject._taps = null;
234
235        return newObject;
236    }
237
238    // FIXME: State update should occur in postfire.
239
240    /** Consume the inputs and produce the outputs of the FIR filter.
241     *  @exception IllegalActionException If parameter values are invalid,
242     *   or if there is no director, or if runtime type conflicts occur.
243     */
244    @Override
245    public void fire() throws IllegalActionException {
246        super.fire();
247
248        // Phase keeps track of which phase of the filter coefficients
249        // are used. Starting phase depends on the _decimationPhaseValue value.
250        int phase = _decimationValue - _decimationPhaseValue - 1;
251
252        // Transfer _decimationValue inputs to _data[]
253        for (int inC = 1; inC <= _decimationValue; inC++) {
254            if (--_mostRecent < 0) {
255                _mostRecent = _data.length - 1;
256            }
257
258            // Note explicit type conversion, which is required to generate
259            // code.
260            //            _data[_mostRecent] = output.getType().convert(input.get(0));
261            _data[_mostRecent] = input.get(0);
262        }
263
264        // Interpolate once for each input consumed
265        for (int inC = 1; inC <= _decimationValue; inC++) {
266            // Produce however many outputs are required
267            // for each input consumed
268            while (phase < _interpolationValue) {
269                _outToken = _zero;
270
271                // Compute the inner product.
272                for (int i = 0; i < _phaseLength; i++) {
273                    int tapsIndex = i * _interpolationValue + phase;
274
275                    int dataIndex = (_mostRecent + _decimationValue - inC + i)
276                            % _data.length;
277
278                    if (tapsIndex < _taps.length) {
279                        _tapItem = _taps[tapsIndex];
280                        _dataItem = _data[dataIndex];
281                        _dataItem = _tapItem.multiply(_dataItem);
282                        _outToken = _outToken.add(_dataItem);
283                    }
284
285                    // else assume tap is zero, so do nothing.
286                }
287
288                output.send(0, _outToken);
289                phase += _decimationValue;
290            }
291
292            phase -= _interpolationValue;
293        }
294    }
295
296    /** Perform domain-specific initialization by calling the
297     *  initialize(Actor) method of the director. The director may
298     *  reject the actor by throwing an exception if the actor is
299     *  incompatible with the domain.
300     *  Set a flag that reinitializes the data buffer at the first firing.
301     *  @exception IllegalActionException If the superclass throws it.
302     */
303    @Override
304    public void initialize() throws IllegalActionException {
305        super.initialize();
306
307        // Must be sure to throw away the old data buffer.
308        _data = null;
309
310        // If this object was created by cloning, then the _taps
311        // variable may be null.
312        _initializeTaps();
313    }
314
315    /** Return false if the input does not have enough tokens to fire.
316     *  Otherwise, return what the superclass returns.
317     *  @return False if the number of input tokens available is not at least
318     *   equal to the decimation parameter.
319     *  @exception IllegalActionException If the superclass throws it.
320     */
321    @Override
322    public boolean prefire() throws IllegalActionException {
323        // If an attribute has changed since the last fire(), or if
324        // this is the first fire(), then reinitialize.
325        if (_reinitializeNeeded) {
326            _reinitialize();
327        }
328
329        if (input.hasToken(0, _decimationValue)) {
330            return super.prefire();
331        }
332        if (_debugging) {
333            _debug("Called prefire(), which returns false.");
334        }
335
336        return false;
337    }
338
339    ///////////////////////////////////////////////////////////////////
340    ////                         protected methods                 ////
341
342    /** Set the output to be &ge; the monotonic function of the input port type.
343     *  @return A set of type constraints
344     */
345    @Override
346    protected Set<Inequality> _customTypeConstraints() {
347        Set<Inequality> result = new HashSet<Inequality>();
348        result.add(
349                new Inequality(new OutputTypeFunction(), output.getTypeTerm()));
350        return result;
351    }
352
353    /** Initialize the taps.
354     *  @exception IllegalActionException If we can't get the token from
355     *  the parameter taps.
356     *
357     */
358    protected void _initializeTaps() throws IllegalActionException {
359        ArrayToken tapsToken = (ArrayToken) taps.getToken();
360        _taps = tapsToken.arrayValue();
361
362        // Get a token representing zero in the appropriate type.
363        _zero = _taps[0].zero();
364
365        _reinitializeNeeded = true;
366    }
367
368    /** Reinitialize local variables in response to changes in attributes.
369     *  @exception IllegalActionException If there is a problem reinitializing.
370     */
371    protected void _reinitialize() throws IllegalActionException {
372        if (_decimationPhaseValue >= _decimationValue) {
373            throw new IllegalActionException(this,
374                    "Invalid decimationPhase: " + _decimationPhaseValue
375                            + ". Must be less than decimation: "
376                            + _decimationValue + ".");
377        }
378
379        _phaseLength = _taps.length / _interpolationValue;
380
381        if (_taps.length % _interpolationValue != 0) {
382            _phaseLength++;
383        }
384
385        // Create new data array and initialize index into it.
386        // Avoid losing the data if possible.
387        // NOTE: If the filter length increases, then it is impossible
388        // to correctly initialize the delay line to contain previously
389        // seen data, because that data has not been saved.
390        int length = _phaseLength + _decimationValue;
391
392        if (_data == null) {
393            _data = new Token[length];
394
395            for (int i = 0; i < length; i++) {
396                _data[i] = _zero;
397            }
398
399            _mostRecent = _phaseLength;
400        } else if (_data.length != length) {
401            Token[] _oldData = _data;
402            _data = new Token[length];
403
404            for (int i = 0; i < length; i++) {
405                if (i < _oldData.length) {
406                    _data[i] = _oldData[i];
407                } else {
408                    _data[i] = _zero;
409                }
410            }
411
412            _mostRecent = _phaseLength;
413        }
414
415        _reinitializeNeeded = false;
416    }
417
418    ///////////////////////////////////////////////////////////////////
419    ////                         protected variables               ////
420
421    /** The delay line. */
422    protected Token[] _data;
423
424    /** The index into the delay line of the most recent input. */
425    protected int _mostRecent;
426
427    /** The phaseLength is ceiling(length/interpolation), where
428     *  length is the number of taps.
429     */
430    protected int _phaseLength;
431
432    /** Decimation value. */
433    protected int _decimationValue = 1;
434
435    /** Interpolation value. */
436    protected int _interpolationValue = 1;
437
438    /** DecimationPhase value. */
439    protected int _decimationPhaseValue = 0;
440
441    /** Indicator that at least one attribute has been changed
442     *  since the last initialization.
443     */
444    protected boolean _reinitializeNeeded = true;
445
446    /** Local cache of the tap values. */
447    protected Token[] _taps;
448
449    /** Local cache of the zero token. */
450    protected Token _zero;
451
452    ///////////////////////////////////////////////////////////////////
453    ////                         private variables                 ////
454    // The tokens needed in FIR
455    private Token _outToken;
456
457    private Token _tapItem;
458
459    private Token _dataItem;
460
461    ///////////////////////////////////////////////////////////////////
462    ////                         inner classes                     ////
463
464    /** This class implements a monotonic function of the input port
465     *  type.  The result type of this actor is generally the input type,
466     *  unless the input type is a FixType, in which case the output
467     *  type will be a FixType with (in most cases) a different precision.
468     */
469    private class OutputTypeFunction extends MonotonicFunction {
470
471        ///////////////////////////////////////////////////////////////
472        ////                       public inner methods            ////
473
474        /** Return the function result.
475         *  @return A Type.
476         */
477        @Override
478        public Object getValue() {
479            Type inputType = input.getType();
480            Type tapsElementType = BaseType.UNKNOWN;
481            Type tapsType = taps.getType();
482            if (!tapsType.equals(BaseType.UNKNOWN)) {
483                if (tapsType instanceof ArrayType) {
484                    tapsElementType = ((ArrayType) tapsType).getElementType();
485                } else {
486                    tapsElementType = BaseType.UNKNOWN;
487                }
488            }
489            Type productType = inputType.multiply(tapsElementType);
490            Type outputType = productType;
491
492            // Taps are normally initialized  in attributeChanged when they change.
493            // Evaluation of parameters however is a lazy process and hence if the public
494            // parameter taps is not evaluated for some reason the private member
495            // variable _taps is not initialized which results in a crash when
496            // getValue is called.
497            // To avoid this we explicitly initialize the taps here.
498            // There is however an issue. When _initializeTaps is executed, the
499            // public parameter taps will be executed, which results in a call of
500            // attributeChanged, which will call _initializeTaps and hence the taps
501            // are now initialized twice.
502            if (_taps == null) {
503                try {
504                    _initializeTaps();
505                } catch (IllegalActionException e) {
506                    throw new IllegalStateException(e);
507                }
508            }
509
510            int phaseLength = _taps.length / _interpolationValue;
511
512            if (_taps.length % _interpolationValue != 0) {
513                phaseLength++;
514            }
515            for (int i = 0; i < phaseLength; i++) {
516                outputType = outputType.add(productType);
517            }
518            return outputType;
519        }
520
521        /** Return the variables in this term. If the type of the input port
522         *  is a variable, return a one element array containing the
523         *  InequalityTerm of that port; otherwise, return an array of zero
524         *  length.
525         *  @return An array of InequalityTerm.
526         */
527        @Override
528        public InequalityTerm[] getVariables() {
529            try {
530                InequalityTerm elementTerm = ArrayType.elementType(taps);
531                if (input.getTypeTerm().isSettable()
532                        && elementTerm.isSettable()) {
533                    InequalityTerm[] variable = new InequalityTerm[2];
534                    variable[0] = input.getTypeTerm();
535                    variable[1] = elementTerm;
536                    return variable;
537                } else if (elementTerm.isSettable()) {
538                    InequalityTerm[] variable = new InequalityTerm[1];
539                    variable[0] = elementTerm;
540                    return variable;
541                } else if (input.getTypeTerm().isSettable()) {
542                    InequalityTerm[] variable = new InequalityTerm[1];
543                    variable[0] = input.getTypeTerm();
544                    return variable;
545                } else {
546                    return new InequalityTerm[0];
547                }
548            } catch (IllegalActionException e) {
549                throw new InternalErrorException(e);
550            }
551        }
552    }
553}