001/* Calculate the coefficients of a linear predictor.
002
003 Copyright (c) 1997-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.actor.TypedAtomicActor;
031import ptolemy.actor.TypedIOPort;
032import ptolemy.data.ArrayToken;
033import ptolemy.data.DoubleToken;
034import ptolemy.data.type.ArrayType;
035import ptolemy.data.type.BaseType;
036import ptolemy.kernel.CompositeEntity;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.NameDuplicationException;
039import ptolemy.math.SignalProcessing;
040
041///////////////////////////////////////////////////////////////////
042//// LevinsonDurbin
043
044/**
045 This actor uses the Levinson-Durbin algorithm to compute the linear
046 predictor coefficients of a random process, given its autocorrelation
047 function as an input.
048
049 <p>These coefficients are produced both in
050 tapped delay line form (on the <i>linearPredictor</i> output) and in
051 lattice filter form (on the <i>reflectionCoefficients</i> output).
052 The <i>order</i> of the predictor (the number of <i>linearPredictor</i>
053 and coefficients <i>reflectionCoefficients</i> produced) is the
054 number of lags of the supplied autocorrelation.
055 The <i>errorPower</i> output is the power of the prediction error
056 as a function of the predictor order.
057 The inputs and outputs are all arrays of doubles.</p>
058
059 <p>The autocorrelation estimates provided as inputs can be generated
060 by the Autocorrelation actor. It the Autocorrelation actor is set
061 so that its <i>biased</i> parameter is true, then the combined
062 effect of that actor and this one is called the autocorrelation
063 method. The <i>order</i> of the predictor is the value of the
064 <i>numberOfLags</i> parameter of the Autocorrelation actor.
065 If the length of the autocorrelation input is odd, then it is assumed
066 to be a symmetric autocorrelation function, and the <i>order</i> of the
067 predictor calculated by this actor is (length + 1)/2.  Otherwise,
068 the <i>order</i> is 1 + (length/2), which assumes that discarding the last
069 sample of the autocorrelation would make it symmetric.</p>
070 <p>
071 Three output signals are generated by this actor. On the
072 <i>errorPower</i> output port, an array of length <i>order</i> + 1
073 gives the prediction error power for each predictor order from zero
074 to <i>order</i>. The first value in this array, which corresponds
075 to the zeroth-order predictor, is simply the zero-th lag of the
076 input autocorrelation, which is the power of the random process
077 with that autocorrelation. Note that for signals without noise
078 whose autocorrelations are estimated by the Autocorrelation actor,
079 the <i>errorPower</i> output can get small. If it gets close
080 to zero, or goes negative, this actor fixes it at zero.
081 "Close to" is determined by the close() method of the
082 ptolemy.math.SignalProcessing class.</p>
083 <p>
084 The <i>linearPredictor</i> output gives the coefficients of an
085 FIR filter that performs linear prediction for the random process.
086 This set of coefficients is suitable for directly feeding a
087 VariableFIR actor, which accepts outside coefficients.
088 The number of coefficients produced is equal to the <i>order</i>.
089 The predictor coefficients produced by this actor can be
090 used to create a maximum-entropy spectral estimate of the input
091 to the Autocorrelation actor.  They can also be used for
092 linear-predictive coding, and any number of other applications.</p>
093 <p>
094 The <i>reflectionCoefficients</i> output is the reflection
095 coefficients, suitable for feeding directly to a VariableLattice
096 actor, which will then generate the forward and backward prediction error.
097 The number of coefficients produced is equal to the <i>order</i>.</p>
098 <p>
099 Note that the definition of reflection coefficients is not quite
100 universal in the literature. The reflection coefficients in
101 reference [2] is the negative of the ones generated by this actor,
102 which correspond to the definition in most other texts,
103 and to the definition of partial-correlation (PARCOR)
104 coefficients in the statistics literature.</p>
105 <p><b>References</b></p>
106 <p>[1]
107 J. Makhoul, "Linear Prediction: A Tutorial Review",
108 <i>Proc. IEEE</i>, vol. 63, pp. 561-580, Apr. 1975.</p>
109 <p>[2]
110 S. M. Kay, <i>Modern Spectral Estimation: Theory &amp; Application</i>,
111 Prentice-Hall, Englewood Cliffs, NJ, 1988.</p>
112
113 @see ptolemy.domains.sdf.lib.Autocorrelation
114 @see ptolemy.domains.sdf.lib.VariableFIR
115 @see ptolemy.domains.sdf.lib.VariableLattice
116 @see ptolemy.math.SignalProcessing
117
118 @author Edward A. Lee
119 @version $Id$
120 @since Ptolemy II 1.0
121 @Pt.ProposedRating Yellow (eal)
122 @Pt.AcceptedRating Red (cxh)
123 */
124public class LevinsonDurbin extends TypedAtomicActor {
125    /** Construct an actor in the specified container with the specified
126     *  name.
127     *  @param container The container.
128     *  @param name The name.
129     *  @exception IllegalActionException If the actor cannot be contained
130     *   by the proposed container.
131     *  @exception NameDuplicationException If the name coincides with
132     *   an actor already in the container.
133     */
134    public LevinsonDurbin(CompositeEntity container, String name)
135            throws IllegalActionException, NameDuplicationException {
136        super(container, name);
137
138        autocorrelation = new TypedIOPort(this, "autocorrelation", true, false);
139        errorPower = new TypedIOPort(this, "errorPower", false, true);
140        linearPredictor = new TypedIOPort(this, "linearPredictor", false, true);
141        reflectionCoefficients = new TypedIOPort(this, "reflectionCoefficients",
142                false, true);
143
144        // FIXME: Can the inputs be complex?
145        autocorrelation.setTypeEquals(new ArrayType(BaseType.DOUBLE));
146        errorPower.setTypeEquals(new ArrayType(BaseType.DOUBLE));
147        linearPredictor.setTypeEquals(new ArrayType(BaseType.DOUBLE));
148        reflectionCoefficients.setTypeEquals(new ArrayType(BaseType.DOUBLE));
149    }
150
151    ///////////////////////////////////////////////////////////////////
152    ////                     ports and parameters                  ////
153
154    /** The autocorrelation input, which is an array.
155     */
156    public TypedIOPort autocorrelation;
157
158    /** The output for the error power, as a function of the predictor
159     *  order.  This produces an array.
160     */
161    public TypedIOPort errorPower;
162
163    /** The output for linear predictor coefficients.
164     *  This produces an array.
165     */
166    public TypedIOPort linearPredictor;
167
168    /** The output for lattice filter coefficients for a prediction
169     *  error filter.  This produces an array.
170     */
171    public TypedIOPort reflectionCoefficients;
172
173    ///////////////////////////////////////////////////////////////////
174    ////                         public methods                    ////
175
176    /** Consume the autocorrelation input, and calculate the predictor
177     *  coefficients, reflection coefficients, and prediction error power.
178     *  @exception IllegalActionException If there is no director.
179     */
180    @Override
181    public void fire() throws IllegalActionException {
182        super.fire();
183        ArrayToken autocorrelationValue = (ArrayToken) autocorrelation.get(0);
184        int autocorrelationValueLength = autocorrelationValue.length();
185
186        // If the length of the input is odd, then the order is
187        // (length + 1)/2. Otherwise, it is 1 + length/2.
188        // Both numbers are the result of integer division
189        // (length + 2)/2.
190        int order = autocorrelationValueLength / 2;
191
192        DoubleToken[] power = new DoubleToken[order + 1];
193        DoubleToken[] refl = new DoubleToken[order];
194        DoubleToken[] lp = new DoubleToken[order];
195        double[] a = new double[order + 1];
196        double[] aP = new double[order + 1];
197        double[] r = new double[order + 1];
198
199        a[0] = 1.0;
200        aP[0] = 1.0;
201
202        // For convenience, read the autocorrelation lags into a vector.
203        for (int i = 0; i <= order; i++) {
204            r[i] = ((DoubleToken) autocorrelationValue
205                    .getElement(autocorrelationValueLength - order + i - 1))
206                            .doubleValue();
207        }
208
209        // Output the zeroth order prediction error power, which is
210        // simply the power of the input process.
211        double P = r[0];
212        power[0] = new DoubleToken(P);
213
214        double gamma;
215
216        // The order recurrence
217        for (int M = 0; M < order; M++) {
218            // Compute the new reflection coefficient.
219            double deltaM = 0.0;
220
221            for (int m = 0; m < M + 1; m++) {
222                deltaM += a[m] * r[M + 1 - m];
223            }
224
225            // Compute and output the reflection coefficient
226            // (which is also equal to the last AR parameter).
227            if (SignalProcessing.close(P, 0.0)) {
228                aP[M + 1] = gamma = 0.0;
229            } else {
230                aP[M + 1] = gamma = -deltaM / P;
231            }
232
233            refl[M] = new DoubleToken(-gamma);
234
235            for (int m = 1; m < M + 1; m++) {
236                aP[m] = a[m] + gamma * a[M + 1 - m];
237            }
238
239            // Update the prediction error power.
240            P = P * (1.0 - gamma * gamma);
241
242            if (P < 0.0 || SignalProcessing.close(P, 0.0)) {
243                P = 0.0;
244            }
245
246            power[M + 1] = new DoubleToken(P);
247
248            // Swap a and aP for next order recurrence.
249            double[] temp = a;
250            a = aP;
251            aP = temp;
252        }
253
254        // Generate the lp outputs.
255        for (int m = 1; m <= order; m++) {
256            lp[m - 1] = new DoubleToken(-a[m]);
257        }
258
259        linearPredictor.broadcast(new ArrayToken(BaseType.DOUBLE, lp));
260        reflectionCoefficients.broadcast(new ArrayToken(BaseType.DOUBLE, refl));
261        errorPower.broadcast(new ArrayToken(BaseType.DOUBLE, power));
262    }
263
264    /** If there is no token on the <i>autocorrelation</i> input, return
265     *  false. Otherwise, return whatever the base class returns.
266     *  @exception IllegalActionException If the base class throws it.
267     *  @return True if it is ok to continue.
268     */
269    @Override
270    public boolean prefire() throws IllegalActionException {
271        if (!autocorrelation.hasToken(0)) {
272            return false;
273        }
274
275        return super.prefire();
276    }
277}