fit := proc(x, y, f, v) local X,Y,V,i,k,m,n,A,b,result,FF,F,p,c,r,z;
#
# FUNCTION: fit - least-squares fit to data
# 
# CALLING SEQUENCE:
#    fit(x, y, f, v);
# 
# PARAMETERS:
#    x    - list or vector of numerical values (data points) or
#         - list of lists, vector of vectors, matrix of numerical values
#    y    - list or vector of numerical values (function values at the points x)
#    f    - list or set or vector of expressions in v (linear basis) or
# 	        expression in v (containing free parameters)
#    v    - name or list of names
# 
#                                    DWG October 1991
#
    V := v;
    if type(V,name) then V := [V] fi;
    if not type(V,list(name)) then
        ERROR(`4th argument must be a name or list of names`) fi;

    if nops(V) = 0 then
        ERROR(`4th argument must be a name or a non empty list of names!`)
    elif type(v,name) then
        if type(x,'list(algebraic)') then
            X := x;
            if not type(y,'list(algebraic)') then 
                ERROR(`1st and 2nd argument must be of same type`) fi;
            Y := y;
        elif type(x,'vector(algebraic)') then
            X := convert(x,list);
            if not type(y,'vector(algebraic)') then 
                ERROR(`1st and 2nd argument must be of same type`) fi;
            Y := convert(y,list);
        else ERROR(`1st argument must be either a list or vector of numerical values`)
        fi
    else
        if type(x,list(list(algebraic))) then
            X := x;
            if not type(y,'list(algebraic)') then 
                ERROR(`2nd argument should be a list`) fi;
            Y := y;
        elif type(x,matrix(algebraic)) then
            X := convert(x,listlist);
            if not type(y,'vector(algebraic)') then 
                ERROR(`if 1st argument is a matrix, then 2nd argument must be a vector`) fi;
            Y := convert(y,list);
        else ERROR(`1st argument is invalid, must be either a list of lists or a matrix`)
      fi
    fi;
    if nops(X) <> nops(Y) then ERROR(`length of 1st and 2nd argument are not equal`) fi;

    if type(f, {list, set, vector}) then
        F := sum(z[i]*f[i], i=1..nops(convert(f,list)) )
    elif type(f, {algebraic}) then
        F := f
    else
        ERROR(`3rd argument must be an expression`) fi;

    c := indets(indets(F),name) minus {V[]};
    m := nops(X);
    n := nops(c);
    p := nops(V);
    FF:= unapply(F, V[]);
    r := linalg[vector](m);
    if type(X,listlist) then
        for i to m do
            if nops(X[i]) <> p then
                ERROR(`data must be of dimenstion`,p) fi;
            r[i] := evalf(FF(X[i][])-Y[i])
        od;
    else
        for i to m do
             r[i] := evalf(FF(X[i])-Y[i])
        od;
    fi;
    map(type,convert(r,set),'linear'(c));
    if has(",false) then     # nonlinear case!
#        F := linalg['dotprod'](r,r);
#        g := linalg['grad'](F,convert(c,list));
#        g := fsolve(convert(g,'set'), c );
#        if g = NULL then ERROR(`could not solve ...`) fi;
#        subs(g,f)
       ERROR(`non linear case not yet implemented`);
    else            # linear case!
        A := linalg[matrix](m,n);
        b := linalg[vector](m);
        for i to m do
            r[i] := collect(r[i],c,'distributed');
            for k to n do
                A[i,k] := coeff(r[i], c[k], 1);
                r[i] := r[i] - c[k] * ";
            od;
            b[i] := -expand(r[i])
        od;
        result := linalg['leastsqrs'](A,b);
        if assigned(result) then
            subs({seq(c[i]=result[i], i=1..n)}, F)
        fi;
fi;
end:

`help/text/fit` := TEXT(
`FUNCTION: fit - least-squares fit to data`,
`   `,
`CALLING SEQUENCE:`,
`   fit(x, y, f, v);`,
`   `,
`PARAMETERS:`,
`   x    - list or vector of numerical values (data points) or`,
`        - list of lists, or matrix of numerical values`,
`   y    - list or vector of numerical values (function values at the points x)`,
`   f    - list or set or vector of expressions in v (linear basis) or`,
`            expression in v (containing free parameters)`,
`   v    - name or list of names`,
`   `,
`SYNOPSIS:   `,
`- The function fit returns the least-squares fit of the curve f or the`,
`  linear combination of the basis functions in f to the set of data points`,
`  given by x and y.  A least squares fit minimizes the sum of the squares`,
`  of the discrepancies at the data points, i.e. minimizes`,
`   `,
`        sum( (f(x[i])-y[i])^2, i=1..n ).`,
`   `,
`- The parameter x is a list (or vector) of the coordinates of the function `,
`  values in the list y.  If f is an expression, it should depend on the data`,
`  variables specified in v, and on one or more parametric variables.`,
`  The dependence on the parametric variables should be linear; the`,
`  dependence on the data variables can either be linear or nonlinear.`,
`  Otherwise if f is a set of basis functions, the functions must depend`,
`  only on v but may be non-linear in v.`,
`   `,
`- If the function f depends on 1 parameter, v must be a name and x must`,
`  be a list or vector of numerical values.  The call to fit looks like`,
`       fit([x1, x2, ..., xn], [y1, y2, ..., yn], f, v)`,
`   `,
`- If the function f depends on m (m>1) parameters, x must be a list of `,
`  lists (or matrix) of dimension m.  The call to fit then looks like`,
`       fit([[x11, x12, ..., x1m], ..., [xn1, xn2, ..., xnm]], `,
`           [y1, y2, ..., yn], f, [x1, x2, ..., xm])`,
`  The order of the coordinates of the points in x is specified by`,
`  the order of the data variables in v.`,
`   `,
`- If you have only a few data points, then by including enough parameters`,
`  in the function f, you can get a fit which exactly reproduces the data `,
`  you have.`,
`   `,
`EXAMPLES:   `,
`   `,
`# fit a parabolic curve through sin(t) from 0 to Pi`,
`> x := [seq(Pi*i/10, i=0..10)]:`,
`> y := map(sin,x):`,
`> fit(x, y, [1, t, t^2], t);`,
`   `,
`                                                              2`,
`                 - .0255783026 + 1.272296748 t - .4049846340 t`,
`   `,
`> fit(x, y, c0+c1*t+c2*t^2, t);`,
`   `,
`                                                              2`,
`                 - .0255783026 + 1.272296748 t - .4049846340 t`,
`   `,
`# the dependence on the data variables need not be linear:`,
`> fit([-4, -1, 2], [-1, -1.9, 2.25], [arctan(t), sin(t)], t); `,
`   `,
`                   1.297230859 arctan(t) + .9613787149 sin(t)`,
`    `,
`# fit a polynomial of degree 4 through the first 20 primes (this`,
`# problem is very ill-conditionned)`,
`> primes := [seq(ithprime(i), i=1..20)]:`,
`> fit([$1..20], primes, [seq(t^i, i=0..4)], t);`,
`   `,
`                                                    2                 3`,
`         1.155056760 + .3640871802 t + .3556525767 t  - .01538563383 t`,
`   `,
`                                4`,
`              + .0002729118483 t`,
`   `,
`# fit exp(a*t+b) through the following list of data:`,
`> x := [-2.0,-1.5,-1.0,-0.5, 0.0, 0.5, 1.0, 1.5, 2.0]:`,
`> y := [ 1.0, 1.3, 1.6, 2.1, 2.7, 3.5, 4.5, 5.8, 7.4]:`,
`> exp(fit(x, map(ln,y), a*t+b, t));`,
`   `,
`                        exp(.5023791374 t + .9981928103)`,
`   `,
`# A two dimensional fit through the surface 1 + 5*u - u*v:`,
`> x := [ seq( seq([i/4, j/4], i=0..4), j=0..4) ]:`,
`> y := map(z -> 1 + 5*z[1] - z[1]*z[2], x):`,
`> fit(x, y, c1 + c2*u + c3*v + c4*u*v, [u, v]);`,
`   `,
`                 1.000000000 + 5.000000000 u - 1.000000000 u v`,
`   `,
`# An exact fit:`,
`> fit([-1.5,0.5,1.5],[0,-2,-1.5], a*t^2+b*t+c, t);`,
`   `,
`                               2`,
`                  .4999999997 t  - .5000000001 t - 1.874999999`,
`   `
):

#save `fit.m`;
#quit;
