View Raw SPL
/*****************************************************************************
*                                                                            *
*   LINFIT.SPL    Copyright (C) 2010 DSP Development Corporation             *
*                               All Rights Reserved                          *
*                                                                            *
*   Author:       Randy Race                                                 *
*                                                                            *
*   Synopsis:     Solves A *^ y = c using QR, SVD or normal equations        *
*                                                                            *
*   Revisions:    11 Jan 2010  RRR  Creation                                 *
*                                                                            *
*****************************************************************************/

#include 

#if @HELP_LINFIT

    LINFIT

    Purpose: Performs general least squares fitting of basis functions 
             using QR decomposition, SVD or normal equations.

    Syntax:  LINFIT(A, y, method)

                  A - input array, the basis function design matrix

                  y - input series, the Y output series

             method - a string, the fitting method

                        "qr"   - QR decomposition (default)
                        "svd"  - singular value decomposition
                        "norm" - normal equations

    Returns: A series, the basis function coefficients.

    Example:
             x = {-1, 1, 1.5, 3};
             y = {6, 2, 2.25, 6};

             c1 = polyfit(y, x, 2);
             c2 = linfit(ravel(ones(length(x),1), x, x^2), y);

             c1 == c2 == {3, -2, 1};

             The first method uses POLYFIT to fit a quadratic
             polynomial to the data points. As shown, LINFIT produces
             the same result. The fitted equation becomes:

             y(x) = 3 -2 * x + x^2

    Example:
             W1: gnorm(100,.01)
             W2: 5*w1 + 3*sin(w1) - 2*w1^3
             W3: ravel(w1, sin(w1), w1^3)
             W4: linfit(W3, w2)

             W4 == {5, 3, -2}, the coefficients of W2.

             In this example, we wish to find the coefficients of the
             relation:

             y(x) = c1 * x + c2 * sin(x) + c3 * x^3 

             Notice that although each term is not necessarily linear,
             the equation as a whole is a linear combination of terms. 
             LINFIT solves the set of simultaneous equations to yield:

             c1 ==  5
             c2 ==  3
             c3 == -2
 
    Remarks:

             For the least squares problem, we wish to find the coefficients
             c that minimize:

                          |A *^c - y|^2

             Where A is the design matrix and y is the output vector. 

             By taking the partial derivatives with respect to each
             coefficient, the least squares problem becomes:

                         A' *^ A = A' *^ y

             The equations or known as the normal equations for the least
             squares problem.

             LINFIT(A, y, "norm") solves for the normal equations
             directly.  However, the normal equations can often be
             ill-conditioned and solving for the coefficients directly
             can be susceptible to roundoff errors.

             To investigate an alternative solution to the least
             squares problem, notice that the least squares merit function
             is minimized to 0 when:

                          A *^ c = y

             LINFIT(A, y) or LINFIT(A, y, "qr") solves the set of
             simultaneous equations by QR decomposition where A is
             an N x M array, y is a vector of length N, N is the number
             of data points and M is the number of basis functions.

             Using QR decomposition, matrix A is expressed as the
             product of an N x N orthogonal matrix Q and a N x M matrix
             R where R is an upper triangular matrix such that the last
             N-M rows contain zeros.  

             LINFIT calculates:

                Q' *^ Q *^ R *^ c = Q' *^ y

                R *^ c = Q' *^ y

            Since R is an upper triangular matrix, the system can be solved
            with simple back substitution.

            Singular value decomposition decomposes the design matrix A
            into three matrix components, U, W and V such that:

                A = U *^ W *^ V'

            where U is an orthonormal matrix that contains the left
            singular values, W is a diagonal matrix that contains the
            weighting factor or singular values and V is a orthonormal
            matrix that contains the right singular values.

            With this decomposition, the least squares problem can be
            solved with:

                c = V *^ diag(diag(W)) *^ U' *^ y

            LINFIT(A, y, "svd") uses singular value decomposition to
            solve the over-determined set of simultaneous equations as
            specified by A and y.  The method is usually slower than QR
            decomposition, but can be more stable for severely
            ill-conditioned systems.

            See [1] for further details.

    See Also:
             \^
             POLYFIT
             QR
             SVD
             SVDDIV

    References:

             [1] Press, Flannery, Teukolsky, Vetterling
                 Numerical Recipes in C
                 2nd Edition
                 Cambridge Press, 1988, 1992
                 pp. 676-680
#endif



/* solve A *^ c = y using QR decomposition */
linfit(A, y, method)
{
        local c;

        if (argc < 3)
        {
                if (argc < 2) error("linfit - two input matrices required");

                method = "qr";
        }

        switch (tolower(method))
        {
                case "qr":
                default:
                        /* solve overdetermined system via QR */
                        c = A \^ y;
                        break;

                case "svd":
                        /* solve overdetermined system via SVD */
                        c = svddiv(A, y);
                        break;

                case "norm":
                        /* solve via normal equations */
                        c = crossprod(A) \^ crossprod(A, y);
                        break;
        }

        return(c);
}