001/* An IIR filter actor that uses a direct form II implementation.
002
003 Copyright (c) 1998-2015 The Regents of the University of California and
004 Research in Motion Limited.
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 OR RESEARCH IN MOTION
013 LIMITED BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
014 INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS
015 SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
016 OR RESEARCH IN MOTION LIMITED HAVE BEEN ADVISED OF THE POSSIBILITY OF
017 SUCH DAMAGE.
018
019 THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION LIMITED
020 SPECIFICALLY DISCLAIM ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
022 PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
023 BASIS, AND THE UNIVERSITY OF CALIFORNIA AND RESEARCH IN MOTION
024 LIMITED HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
025 ENHANCEMENTS, OR MODIFICATIONS.
026 PT_COPYRIGHT_VERSION_2
027 COPYRIGHTENDKEY
028
029 */
030package ptolemy.actor.lib;
031
032import ptolemy.data.ArrayToken;
033import ptolemy.data.Token;
034import ptolemy.data.expr.Parameter;
035import ptolemy.data.type.ArrayType;
036import ptolemy.kernel.CompositeEntity;
037import ptolemy.kernel.util.Attribute;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.Workspace;
041import ptolemy.util.CancelException;
042import ptolemy.util.MessageHandler;
043
044///////////////////////////////////////////////////////////////////
045//// IIR
046
047/**
048
049 This actor is an implementation of an infinite impulse response IIR
050 filter.  A direct form II [1] implementation is used. This actor is type
051 polymorphic. Its input, output,
052 numerator and denominator types can be any type of Token supporting the
053 basic arithmetic operations (add, subtract and multiply).
054 <p>
055 This filter has a transfer function given by:
056
057 <b>References</b>
058 <p>[1]A. V. Oppenheim, R. W. Schafer, <i>Discrete-Time Signal Processing</i>,
059 Prentice Hall, 1989.
060
061 @author Brian K. Vogel, Steve Neuendorffer
062 @author Aleksandar Necakov, Research in Motion Limited
063 @version $Id$
064 @since Ptolemy II 1.0
065 @Pt.ProposedRating Red (vogel)
066 @Pt.AcceptedRating Red (cxh)
067 */
068public class IIR extends Transformer {
069    /** Construct an actor with the given container and name.
070     *  @param container The container.
071     *  @param name The name of this actor.
072     *  @exception IllegalActionException If the actor cannot be contained
073     *   by the proposed container.
074     *  @exception NameDuplicationException If the container already has an
075     *   actor with this name.
076     */
077    public IIR(CompositeEntity container, String name)
078            throws NameDuplicationException, IllegalActionException {
079        super(container, name);
080
081        // Parameters
082        numerator = new Parameter(this, "numerator");
083        numerator.setExpression("{1.0}");
084        attributeChanged(numerator);
085        denominator = new Parameter(this, "denominator");
086        denominator.setExpression("{1.0}");
087        attributeChanged(denominator);
088
089        output.setTypeAtLeast(ArrayType.elementType(numerator));
090        output.setTypeAtLeast(ArrayType.elementType(denominator));
091        input.setTypeAtLeast(output);
092        output.setTypeAtLeast(input);
093    }
094
095    ///////////////////////////////////////////////////////////////////
096    ////                     ports and parameters                  ////
097
098    /** This parameter represents the numerator coefficients as an array
099     *  of tokens. The format is
100     *  {b<sub>0</sub>, b<sub>1</sub>, ..., b<sub>M</sub>}. The default
101     *  value of this parameter is {1.0}.
102     */
103    public Parameter numerator;
104
105    /** This  parameter represents the denominator coefficients as an
106     *  array of a tokens. The format is
107     *  {a<sub>0</sub>, a<sub>1</sub>, ..., a<sub>N</sub>}. Note that
108     *  the value of a<sub>0</sub> is constrained to be 1.0. This
109     *  implementation will issue a warning if it is not.
110     *  The default value of this parameter is {1.0}.
111     */
112    public Parameter denominator;
113
114    ///////////////////////////////////////////////////////////////////
115    ////                         public methods                    ////
116
117    /** Handle parameter change events on the
118     *  <i>numerator</i> and <i>denominator</i> parameters. The
119     *  filter state vector is reinitialized to zero state.
120     *  @param attribute The attribute that changed.
121     *  @exception IllegalActionException If this method is invoked
122     *   with an unrecognized parameter.
123     */
124    @Override
125    public void attributeChanged(Attribute attribute)
126            throws IllegalActionException {
127        if (attribute == numerator) {
128            ArrayToken numeratorValue = (ArrayToken) numerator.getToken();
129            _numerator = numeratorValue.arrayValue();
130        } else if (attribute == denominator) {
131            ArrayToken denominatorValue = (ArrayToken) denominator.getToken();
132            _denominator = denominatorValue.arrayValue();
133
134            // Note: a<sub>0</sub> must always be 1.
135            // Issue a warning if it isn't.
136            if (!_denominator[0].isEqualTo(_denominator[0].one())
137                    .booleanValue()) {
138                try {
139                    MessageHandler.warning(
140                            "First denominator value is required to be 1. "
141                                    + "Using 1.");
142                } catch (CancelException ex) {
143                    throw new IllegalActionException(this,
144                            "Canceled parameter change.");
145                }
146
147                // Override the user and just use 1.
148                _denominator[0] = _denominator[0].one();
149            }
150        } else {
151            super.attributeChanged(attribute);
152            return;
153        }
154
155        // Initialize filter state.
156        if (_numerator != null && _denominator != null) {
157            _initStateVector();
158        }
159    }
160
161    /** Clone the actor into the specified workspace. This calls the
162     *  base class and then sets the type constraints.
163     *  @param workspace The workspace for the new object.
164     *  @return A new actor.
165     *  @exception CloneNotSupportedException If a derived class has
166     *   an attribute that cannot be cloned.
167     */
168    @Override
169    public Object clone(Workspace workspace) throws CloneNotSupportedException {
170        IIR newObject = (IIR) super.clone(workspace);
171
172        try {
173            newObject.output
174                    .setTypeAtLeast(ArrayType.elementType(newObject.numerator));
175            newObject.output.setTypeAtLeast(
176                    ArrayType.elementType(newObject.denominator));
177            newObject.input.setTypeAtLeast(newObject.output);
178            newObject.output.setTypeAtLeast(newObject.input);
179
180            ArrayToken numeratorValue = (ArrayToken) numerator.getToken();
181            newObject._numerator = numeratorValue.arrayValue();
182
183            ArrayToken denominatorValue = (ArrayToken) denominator.getToken();
184            newObject._denominator = denominatorValue.arrayValue();
185        } catch (IllegalActionException ex) {
186            // CloneNotSupportedException does not have a constructor
187            // that takes a cause argument, so we use initCause
188            CloneNotSupportedException throwable = new CloneNotSupportedException();
189            throwable.initCause(ex);
190            throw throwable;
191        }
192
193        newObject._stateVector = new Token[_stateVector.length];
194        System.arraycopy(_stateVector, 0, newObject._stateVector, 0,
195                _stateVector.length);
196
197        return newObject;
198    }
199
200    /** If at least one input token is available, consume a single
201     *  input token, apply the filter to that input token, and
202     *  compute a single output token. If this method is invoked
203     *  multiple times in one iteration, then only the input read
204     *  on the last invocation in the iteration will affect the
205     *  filter state.
206     *
207     *  @exception IllegalActionException Not thrown in this base class.
208     */
209    @Override
210    public void fire() throws IllegalActionException {
211        super.fire();
212        if (input.hasToken(0)) {
213            // Save state vector value.
214            Token savedState = _stateVector[_currentTap];
215
216            // Compute the current output sample given the input sample.
217            Token yCurrent = _computeOutput(input.get(0));
218
219            // Shadowed state. used in postfire().
220            _latestWindow = _stateVector[_currentTap];
221
222            // Restore state vector to previous state.
223            _stateVector[_currentTap] = savedState;
224            output.send(0, yCurrent);
225        }
226    }
227
228    /**  Initialize the filter state vector with zero state.
229     *   @exception IllegalActionException If the base class throws
230     *    it.
231     */
232    @Override
233    public void initialize() throws IllegalActionException {
234        super.initialize();
235
236        // Initialize filter state.
237        _initStateVector();
238        _currentTap = 0;
239    }
240
241    /** Return false if the input does not have a token.
242     *  @return false if the input does not have a token.
243     *  @exception IllegalActionException If thrown by the
244     *  super class or while checking to see if there is a token.
245     */
246    @Override
247    public boolean prefire() throws IllegalActionException {
248        boolean result = super.prefire();
249        return result && input.hasToken(0);
250    }
251
252    /** Update the filter state.
253     *
254     *  @exception IllegalActionException If the base class throws it.
255     */
256    @Override
257    public boolean postfire() throws IllegalActionException {
258        _stateVector[_currentTap] = _latestWindow;
259
260        // Update the state vector pointer.
261        if (--_currentTap < 0) {
262            _currentTap = _stateVector.length - 1;
263        }
264
265        return super.postfire();
266    }
267
268    ///////////////////////////////////////////////////////////////////
269    ////                         private methods                   ////
270    private Token _computeOutput(Token xCurrent) throws IllegalActionException {
271        for (int j = 1; j < _denominator.length; j++) {
272            xCurrent = xCurrent.subtract(_denominator[j].multiply(
273                    _stateVector[(_currentTap + j) % _stateVector.length]));
274        }
275
276        _stateVector[_currentTap] = xCurrent;
277
278        Token yCurrent = _numerator[0].zero();
279
280        for (int k = 0; k < _numerator.length; k++) {
281            yCurrent = yCurrent.add(_numerator[k].multiply(
282                    _stateVector[(_currentTap + k) % _stateVector.length]));
283        }
284
285        return yCurrent;
286    }
287
288    private void _initStateVector() throws IllegalActionException {
289        if (_numerator.length > 0) {
290            int stateSize = java.lang.Math.max(_numerator.length,
291                    _denominator.length);
292            _stateVector = new Token[stateSize];
293
294            Token zero = _numerator[0].zero();
295
296            for (int j = 0; j < _stateVector.length; j++) {
297                _stateVector[j] = zero;
298            }
299        }
300    }
301
302    ///////////////////////////////////////////////////////////////////
303    ////                         private variables                 ////
304    // Filter parameters
305    private Token[] _numerator = new Token[0];
306
307    private Token[] _denominator = new Token[0];
308
309    // Filter state vector
310    private Token[] _stateVector;
311
312    // State vector pointer
313    private int _currentTap;
314
315    // Shadow state.
316    private Token _latestWindow;
317}