001/* A recursive (all pole) lattice 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.actor.lib;
029
030import ptolemy.data.ArrayToken;
031import ptolemy.data.DoubleToken;
032import ptolemy.data.expr.Parameter;
033import ptolemy.data.type.BaseType;
034import ptolemy.kernel.CompositeEntity;
035import ptolemy.kernel.util.Attribute;
036import ptolemy.kernel.util.IllegalActionException;
037import ptolemy.kernel.util.NameDuplicationException;
038import ptolemy.kernel.util.Workspace;
039
040///////////////////////////////////////////////////////////////////
041//// RecursiveLattice
042
043/**
044 A recursive (all-pole) filter with a lattice structure.
045 The coefficients of such a filter are called "reflection coefficients."
046 Recursive lattice filters are typically used as synthesis filters for
047 random processes because it is easy to ensure that they are stable.
048 A recursive lattice filter is stable if its reflection
049 coefficients are all less than unity in magnitude.  To get the
050 reflection coefficients for a linear predictor for a particular
051 random process, you can use the LevinsonDurbin actor.
052 The inputs and outputs are of type double.
053 <p>
054 The default reflection coefficients correspond to the following
055 transfer function:
056 <pre>
057                           1
058 H(z) =  --------------------------------------
059        1 - 2z<sup>-1</sup> + 1.91z<sup>-2</sup> - 0.91z<sup>-3</sup> + 0.205z<sup>-4</sup>
060 </pre>
061 <p>
062 The structure of the filter is as follows:
063 <pre>
064      y[0]          y[1]                 y[n-1]           y[n]
065 X(n) ---(+)-&gt;--o--&gt;----(+)-&gt;--o---&gt;-- ... -&gt;--(+)-&gt;--o---&gt;---o---&gt;  Y(n)
066           \   /          \   /                  \   /        |
067          +Kn /        +Kn-1 /                  +K1 /         |
068             X              X                      X          |
069          -Kn \        -Kn-1 \                  -K1 \         V
070           /   \          /   \                  /   \        |
071         (+)-&lt;--o--[z]--(+)-&lt;--o--[z]- ... -&lt;--(+)-&lt;--o--[z]--/
072                w[1]           w[2]                   w[n]
073 </pre>
074 where the [z] are unit delays and the (+) are adders
075 and "y" and "w" are variables representing the state of the filter.
076 <p>
077 The reflection (or partial-correlation (PARCOR))
078 coefficients should be specified
079 right to left, K1 to Kn as above.
080 Using exactly the same coefficients in the
081 Lattice actor will result in precisely the inverse transfer function.
082 <p>
083 Note that the definition of reflection coefficients is not quite universal
084 in the literature. The reflection coefficients in reference [2]
085 are the negative of the ones used by this actor, which
086 correspond to the definition in most other texts,
087 and to the definition of partial-correlation (PARCOR)
088 coefficients in the statistics literature.
089 The signs of the coefficients used in this actor are appropriate for values
090 given by the LevinsonDurbin actor.
091 <p>
092 <b>References</b>
093 <p>[1]
094 J. Makhoul, "Linear Prediction: A Tutorial Review",
095 <i>Proc. IEEE</i>, Vol. 63, pp. 561-580, Apr. 1975.
096 <p>[2]
097 S. M. Kay, <i>Modern Spectral Estimation: Theory &amp; Application</i>,
098 Prentice-Hall, Englewood Cliffs, NJ, 1988.
099
100 @see ptolemy.actor.lib.IIR
101 @see ptolemy.actor.lib.LevinsonDurbin
102 @see ptolemy.actor.lib.Lattice
103 @see ptolemy.domains.sdf.lib.VariableRecursiveLattice
104 @author Edward A. Lee, Christopher Hylands, Steve Neuendorffer
105 @version $Id$
106 @since Ptolemy II 1.0
107 @Pt.ProposedRating Yellow (cxh)
108 @Pt.AcceptedRating Yellow (cxh)
109 */
110public class RecursiveLattice extends Transformer {
111    /** Construct an actor with the given container and name.
112     *  @param container The container.
113     *  @param name The name of this actor.
114     *  @exception IllegalActionException If the actor cannot be contained
115     *   by the proposed container.
116     *  @exception NameDuplicationException If the container already has an
117     *   actor with this name.
118     */
119    public RecursiveLattice(CompositeEntity container, String name)
120            throws NameDuplicationException, IllegalActionException {
121        super(container, name);
122
123        input.setTypeEquals(BaseType.DOUBLE);
124        output.setTypeEquals(BaseType.DOUBLE);
125
126        reflectionCoefficients = new Parameter(this, "reflectionCoefficients");
127
128        // Note that setExpression() will call attributeChanged().
129        reflectionCoefficients
130                .setExpression("{0.804534, -0.820577, 0.521934, -0.205}");
131    }
132
133    ///////////////////////////////////////////////////////////////////
134    ////                         parameters                        ////
135
136    /** The reflection coefficients.  This is an array of doubles with
137     *  default value {0.804534, -0.820577, 0.521934, -0.205}. These
138     *  are the reflection coefficients for the linear predictor of a
139     *  particular random process.
140     */
141    public Parameter reflectionCoefficients;
142
143    ///////////////////////////////////////////////////////////////////
144    ////                         public methods                    ////
145
146    /** If the argument is the <i>reflectionCoefficients</i> parameter,
147     *  then reallocate the arrays to use.
148     *  @param attribute The attribute that changed.
149     *  @exception IllegalActionException If the base class throws it.
150     */
151    @Override
152    public void attributeChanged(Attribute attribute)
153            throws IllegalActionException {
154        if (attribute == reflectionCoefficients) {
155            ArrayToken value = (ArrayToken) reflectionCoefficients.getToken();
156            int valueLength = value.length();
157
158            if (_backward == null || valueLength != _backward.length - 1) {
159                // Need to allocate or reallocate the arrays.
160                _backward = new double[valueLength + 1];
161                _backwardCache = new double[valueLength + 1];
162                _forward = new double[valueLength + 1];
163                _forwardCache = new double[valueLength + 1];
164                _reflectionCoefficients = new double[valueLength];
165            }
166
167            for (int i = 0; i < valueLength; i++) {
168                _reflectionCoefficients[i] = ((DoubleToken) value.getElement(i))
169                        .doubleValue();
170            }
171        } else {
172            super.attributeChanged(attribute);
173        }
174    }
175
176    /** Clone the actor into the specified workspace.
177     *  @param workspace The workspace for the new object.
178     *  @return A new actor.
179     *  @exception CloneNotSupportedException If a derived class contains
180     *   an attribute that cannot be cloned.
181     */
182    @Override
183    public Object clone(Workspace workspace) throws CloneNotSupportedException {
184        RecursiveLattice newObject = (RecursiveLattice) super.clone(workspace);
185
186        int forwardLength = 0;
187        if (_forward == null) {
188            try {
189                ArrayToken value = (ArrayToken) reflectionCoefficients
190                        .getToken();
191                forwardLength = value.length() + 1;
192            } catch (IllegalActionException ex) {
193                throw new CloneNotSupportedException("Failed to clone: " + ex);
194            }
195        } else {
196            forwardLength = _forward.length;
197        }
198
199        int backwardLength = 0;
200        if (_backward == null) {
201            try {
202                ArrayToken value = (ArrayToken) reflectionCoefficients
203                        .getToken();
204                backwardLength = value.length() + 1;
205            } catch (IllegalActionException ex) {
206                throw new CloneNotSupportedException("Failed to clone: " + ex);
207            }
208        } else {
209            backwardLength = _backward.length;
210        }
211        newObject._backward = new double[backwardLength];
212        newObject._backwardCache = new double[backwardLength];
213        newObject._forward = new double[forwardLength];
214        newObject._forwardCache = new double[forwardLength];
215        newObject._reflectionCoefficients = new double[forwardLength - 1];
216
217        if (_backward != null) {
218            System.arraycopy(_backward, 0, newObject._backward, 0,
219                    backwardLength);
220        }
221        if (_backwardCache != null) {
222            System.arraycopy(_backwardCache, 0, newObject._backwardCache, 0,
223                    _backwardCache.length);
224        }
225        if (_forward != null) {
226            System.arraycopy(_forward, 0, newObject._forward, 0,
227                    _forward.length);
228        }
229        if (_forwardCache != null) {
230            System.arraycopy(_forwardCache, 0, newObject._forwardCache, 0,
231                    _forwardCache.length);
232        }
233        if (_reflectionCoefficients != null) {
234            System.arraycopy(_reflectionCoefficients, 0,
235                    newObject._reflectionCoefficients, 0,
236                    _reflectionCoefficients.length);
237        }
238
239        try {
240            ArrayToken value = (ArrayToken) reflectionCoefficients.getToken();
241            for (int i = 0; i < value.length(); i++) {
242                _reflectionCoefficients[i] = ((DoubleToken) value.getElement(i))
243                        .doubleValue();
244            }
245        } catch (IllegalActionException ex) {
246            // CloneNotSupportedException does not have a constructor
247            // that takes a cause argument, so we use initCause
248            CloneNotSupportedException throwable = new CloneNotSupportedException();
249            throwable.initCause(ex);
250            throw throwable;
251        }
252        return newObject;
253    }
254
255    /** Consume one input token, if there is one, and produce one output
256     *  token.  If there is no input, then produce no output.
257     *  @exception IllegalActionException If there is no director.
258     */
259    @Override
260    public void fire() throws IllegalActionException {
261        super.fire();
262        if (input.hasToken(0)) {
263            DoubleToken inputValue = (DoubleToken) input.get(0);
264
265            // NOTE: The following code is ported from Ptolemy Classic.
266            double k;
267            int M = _backward.length - 1;
268
269            // Forward prediction error
270            _forwardCache[0] = inputValue.doubleValue(); // _forward(0) = x(n)
271
272            for (int i = 1; i <= M; i++) {
273                k = _reflectionCoefficients[M - i];
274                _forwardCache[i] = k * _backwardCache[i] + _forwardCache[i - 1];
275            }
276
277            output.broadcast(new DoubleToken(_forwardCache[M]));
278
279            // Backward:  Compute the w's for the next round
280            for (int i = 1; i < M; i++) {
281                k = -_reflectionCoefficients[M - 1 - i];
282                _backwardCache[i] = _backwardCache[i + 1]
283                        + k * _forwardCache[i + 1];
284            }
285
286            _backwardCache[M] = _forwardCache[M];
287        }
288    }
289
290    /** Initialize the state of the filter.
291     */
292    @Override
293    public void initialize() throws IllegalActionException {
294        // Invoke any initializable methods.
295        super.initialize();
296        for (int i = 0; i < _forward.length; i++) {
297            _forward[i] = 0.0;
298            _forwardCache[i] = 0.0;
299            _backward[i] = 0.0;
300            _backwardCache[i] = 0.0;
301        }
302    }
303
304    /** Update the backward and forward prediction errors that
305     *  were generated in fire() method.
306     *  @return False if the number of iterations matches the number requested.
307     *  @exception IllegalActionException If there is no director.
308     */
309    @Override
310    public boolean postfire() throws IllegalActionException {
311        System.arraycopy(_backwardCache, 0, _backward, 0,
312                _backwardCache.length);
313        System.arraycopy(_forwardCache, 0, _forward, 0, _forwardCache.length);
314        return super.postfire();
315    }
316
317    /** Check to see if this actor is ready to fire.
318     *  @exception IllegalActionException If there is no director.
319     */
320    @Override
321    public boolean prefire() throws IllegalActionException {
322        // Get a copy of the current filter state that we can modify.
323        System.arraycopy(_backward, 0, _backwardCache, 0,
324                _backwardCache.length);
325        System.arraycopy(_forward, 0, _forwardCache, 0, _forwardCache.length);
326        return super.prefire();
327    }
328
329    ///////////////////////////////////////////////////////////////////
330    ////                         private variables                 ////
331    // We set these to null and then the constructor calls setExpression()
332    // which in turn calls attributeChanged() which then allocates
333    // these arrays.
334    // Backward prediction errors, represented by "w" in the class
335    // comment.
336    private double[] _backward = null;
337
338    // Cache of backward prediction errors, represented by "w" in the class
339    // comment.  The fire() method updates _forwardCache and postfire()
340    // copies _forwardCache to _forward so this actor will work in domains
341    // like SR.
342    private double[] _backwardCache = null;
343
344    // Forward prediction errors, represented by "y" in the class
345    // comment.
346    private double[] _forward = null;
347
348    // Cache of forward prediction errors, represented by "y" in the class
349    // comment.  The fire() method updates _forwardCache and postfire()
350    // copies _forwardCache to _forward so this actor will work in domains
351    // like SR.
352    private double[] _forwardCache = null;
353
354    // Cache of reflection coefficients.
355    private double[] _reflectionCoefficients = null;
356}