home *** CD-ROM | disk | FTP | other *** search
- (* :Title: Nonlinear Curve Fitting *)
-
- (* :Context: Statistics`NonlinearFit` *)
-
- (* :Author: John M. Novak *)
-
- (* :Summary: A package to perform nonlinear curve fitting,
- using a variety of algorithms.
- *)
-
- (* :Package Version: 1.1 *)
-
- (* :Mathematica Version: 2.0 *)
-
- (* :History:
- Version 1.0, Oct. 91 by John M. Novak
- Version 1.1, Feb. 92 by John M. Novak - major revisions
- *)
-
- (* :Keywords:
- curve fitting, nonlinear regression
- *)
-
- (* :Sources:
- Janhunen, Pekka, NonlinearFit`, (a Mathematica package), 1990.
- Press, William, et. al., "Numerical Recipies in Pascal",
- pp. 572-580, Cambridge University Press (Cambridge, 1989).
- Withoff, Dave, DataAnalysis`NonlinearRegression`,
- (a Mathematica package), 1989.
- *)
-
- (* :Discussion:
-
- *)
-
- BeginPackage["Statistics`NonlinearFit`",
- "Utilities`FilterOptions`"]
-
- NonlinearFit::usage =
- "NonlinearFit[data,model,vars,params,(opts)] uses iterative
- methods to fit the data to the model containing the given
- variables and parameters. Paramaters may be expressed as
- a list of symbols or {symbol, startingvalue} pairs. The
- data are given as {{x1, y1, ..., f1},{x2, y2, ... f2},...};
- some variations on this are also valid (see documentation.)";
-
- Weights::usage =
- "Weights is an option for NonlinearFit. One can pass in an array
- of weights, one for each point in the data set.";
-
- ShowProgress::usage =
- "ShowProgress is an option for NonlinearFit. If True, it will
- print a summary of intermediate results for each iteration
- of the algorithm.";
-
- LevenbergMarquardt::usage =
- "LevenbergMarquardt is a value for the Method option of NonlinearFit.
- This causes NonlinearFit to use the Levenberg-Marquardt method of
- minimizing chi^2."
-
- Begin["`Private`"]
-
- (* first, a quick private utility... *)
-
- numberQ[n_] := NumberQ[N[n]]
-
- (* NonlinearFit options and messages *)
-
- Options[NonlinearFit] =
- {MaxIterations -> 30,
- Method -> LevenbergMarquardt,
- ShowProgress -> False,
- Weights -> Automatic,
- WorkingPrecision -> $MachinePrecision,
- AccuracyGoal -> 1,
- PrecisionGoal -> 3};
-
- NonlinearFit::badits =
- "MaxIterations -> `1` is not set to a positive integer;
- setting to 30.";
-
- NonlinearFit::badmeth =
- "Method `1` is not available for NonlinearFit; using
- SteepestDescent method.";
-
- NonlinearFit::toomany =
- "Too many variables have been provided for the size of the data;
- given `1`-tuples for data, given `2` variables.";
-
- NonlinearFit::badweights =
- "There is not a valid weight for each data point!
- Setting all weights to 1.";
-
- NonlinearFit::badparams =
- "The form of the parameters `1` given to NonlinearFit is not correct.
- Parameters should be of form {parameter}, {parameter, startpoint}, or
- {parameter, startpoint, minrange, maxrange}.";
-
- NonlinearFit::badwork =
- "The value `1` given for the WorkingPrecision is not a positive
- integer. Setting to $MachinePrecision...";
-
- NonlinearFit::badprec =
- "The value `1` given for the PrecisionGoal is not a positive integer
- or Automatic. Setting to $MachinePrecision - 10...";
-
- NonlinearFit::badacc =
- "The value `1` given for the AccuracyGoal is not a positive integer
- or Automatic. Setting to $MachinePrecision - 10...";
-
- NonlinearFit::degrees =
- "Warning: The data set is smaller than the number of parameters; this is likely
- to generate odd answers...";
-
- NonlinearFit::lmnoconv =
- "Warning: NonlinearFit did not converge in `1` iterations. The
- returned value may not be at the minimum.";
-
- NonlinearFit::lmoutofrng =
- "Warning: the LevenbergMarquardt search exited the range given to the
- parameters. The returned value may not be at the minimum.";
-
- NonlinearFit::lmpnocon =
- "Warning: the values of the parameters given to NonlinearFit do not
- appear to have converged. The returned value may not be at the
- minimum.";
-
- NonlinearFit::lmovfl =
- "Warning: numeric overflow occurred during this search. This may mean
- that the search is being started from an inappropriate point, that your
- model is unstable, or that insufficient precision is being used for these
- calculations. The returned value may not be at the minimum.";
-
- NonlinearFit::fitfail =
- "The fitting algorithm failed utterly, returning `1`.";
-
- (* NonlinearFit arguments:
- data - vector or matrix of numbers;
- model - the model, a symbolic expression;
- vars - a list of symbols, expressing the variables in model;
- inputparams - a list of symbol and {symbol, startingpoint}
- pairs, expressing the parameters and their starting
- points in the model;
- opts - the options
- *)
-
- NonlinearFit[d_,m_,var_Symbol,params_,opts___] :=
- NonlinearFit[d,m,{var},params,opts]
-
- NonlinearFit[d_,m_,v_,
- param:(_Symbol |
- {_Symbol,(_?numberQ | Infinity | -Infinity)..}
- ),
- opts___] :=
- NonlinearFit[d,m,v,{param},opts]
-
- NonlinearFit[data:{{__?numberQ, {__?numberQ}..}..},
- m_,vars_,params_,opts___] :=
- NonlinearFit[
- Map[If[Head[#] === List, First[#], #]&,data,{2}],
- m,vars,params,opts,Weights ->
- Map[If[Head[#] === List, Last[#], 1]&,
- Map[Drop[#,Length[vars]]&,data]
- ]
- ]
-
- NonlinearFit[data_?(VectorQ[N[#],NumberQ]&),
- m_,vars_,params_,opts___] :=
- NonlinearFit[Transpose[Append[(Evaluate[Table[#,
- {Length[vars]}]]&)[Range[Length[data]]],data]],
- m,vars,params,opts]
-
- (* The following routine will catch whether the fit could be
- performed; if not, it will allow the function to return
- unevaluated, rather than returning a meaningless error value.
- (Instead, it returns a meaningless unevaluated expression...:-)
- *)
-
- NonlinearFit[data:_?(MatrixQ[N[#],NumberQ]&),
- model_,vars:{__Symbol},
- inputparams:{(_Symbol |
- {_Symbol,(_?numberQ | Infinity | -Infinity)..})..},
- opts:((_Rule | RuleDelayed)...)] :=
- With[{fitted =
- nonlinearfit[data,model,vars,inputparams,opts]},
- fitted/;fitted =!= $Failed
- ]
-
- nonlinearfit[data_, model_, vars_, inputparams_, opts___] :=
- Module[{maxits,method,progress,weights,params,workprec,accgoal,
- pts,datalen,varlen = Length[vars],minrng,maxrng,min,max,x,
- fmopts,out,start,precgoal,response,s},
- (* Get options *)
- {maxits,method,progress,weights,workprec,accgoal,precgoal} =
- {MaxIterations, Method, ShowProgress, Weights,
- WorkingPrecision, AccuracyGoal, PrecisionGoal}/.
- {opts}/.Options[NonlinearFit];
- fmopts = FilterOptions[FindMinimum,opts];
- (* Option and Argument checking *)
- (* - check input data, separate out response and coords *)
- If[(datalen = Length[First[data]]) - varlen < 1,
- Message[NonlinearFit::toomany,datalen,varlen];
- Return[$Failed]];
- response = Transpose[Map[Drop[#,varlen]&, data]];
- pts = Map[Drop[#,varlen - datalen]&, data];
- (* - set and check weights *)
- If[weights === Automatic,
- weights = Table[1,{#1},{#2}]& @@ Dimensions[response]];
- If[VectorQ[N[weights],NumberQ],
- weights = Table[weights,{Length[response]}]];
- If[Head[weights =!= List],
- weights = Map[weights, response, {2}]];
- If[!MatrixQ[N[weights],NumberQ] ||
- Dimensions[weights] =!= Dimensions[response],
- Message[NonlinearFit::badweights];
- weights = Table[1,{#1},{#2}]& @@ Dimensions[response]
- ];
- (* - check parameters, start points, ranges *)
- {params,start,minrng,maxrng} = Transpose[
- Map[Replace[#,
- {(x_Symbol | {x_Symbol}) -> {x,{1},-Infinity, Infinity},
- {x_Symbol, s_?numberQ} -> {x,{s},-Infinity,Infinity},
- {x_Symbol, min_?numberQ, max_?numberQ} ->
- {x,startposns[min,max],min,max},
- {x_Symbol, min_?numberQ, Infinity} ->
- {x,{min + 1, min}, Infinity},
- {x_Symbol, -Infinity, max_?numberQ} ->
- {x, {max - 1}, -Infinity, max},
- {x_Symbol, -Infinity, Infinity} ->
- {x,{1}, -Infinity, Infinity},
- {x_Symbol, s_?numberQ, min:(-Infinity | _?numberQ),
- max:(_?numberQ | Infinity)} ->
- {x,s,min,max},
- _ -> {$Failed,$Failed,$Failed,$Failed}}]&,
- inputparams]
- ];
- If[(Select[start,(# === $Failed)&] =!= {}) ||
- Not[And @@ Thread[N[minrng <= start <= maxrng]]],
- Message[NonlinearFit::badparams, inputparams];
- Return[$Failed]
- ];
- start = Flatten[Outer[List,##]& @@ start,Length[start] - 1];
- If[Length[start] === 1,
- start = First[start],
- start = findbeststart[start, pts, First[response],
- First[weights], model, vars, params]
- ];
- (* - check degrees of freedom *)
- If[Length[params] > Length[pts],
- Message[NonlinearFit::degrees]
- ];
- (* - check assorted options *)
- If[!(IntegerQ[maxits] && Positive[maxits]),
- Message[NonlinearFit::badits,maxits];
- maxits = 30];
- If[!(IntegerQ[workprec] && Positive[workprec]),
- Message[NonlinearFit::badwork, workprec];
- workprec = $MachinePrecision];
- If[precgoal === Automatic, precgoal = workprec - 10];
- If[accgoal === Automatic, accgoal = workprec - 10];
- If[!(IntegerQ[precgoal] && Positive[precgoal]),
- Message[NonlinearFit::badprec, precgoal];
- precgoal = $MachinePrecision - 10];
- If[!(IntegerQ[accgoal] && Positive[accgoal]),
- Message[NonlinearFit::badacc, accgoal];
- accgoal = $MachinePrecision - 10];
- If[!MemberQ[{LevenbergMarquardt,FindMinimum},method],
- Message[NonlinearFit::badmeth,method];
- method = LevenbergMarquardt];
- progress = TrueQ[progress];
- (* Call proper fitting routine *)
- (* Implementation note: for now, we thread across all the
- responses; none of the algorithms in use can be made
- more efficient when all are done at once. This should
- change if an algorithm that can take advantage of this
- is used... *)
- out = MapThread[
- nlfit[method, pts, #1, #2, model, vars, params, start, minrng,
- maxrng, progress, maxits, workprec, precgoal, accgoal,
- fmopts]&,
- {response,weights}];
- (* check output *)
- If[!MatchQ[out, {{__Rule}..}],
- Message[NonlinearFit::fitfail,out];
- Return[$Failed]
- ];
- If[Length[out] === 1, First[out], out]
- ]
-
- (* SteepestDescent method; creates symbolic form of chi-squared,
- then uses built-in function FindMinimum to minimize this, given
- the starting point.
- *)
-
- nlfit[FindMinimum, data_, response_, weights_, model_, vars_,
- parameters_, start_, minr_, maxr_, prog_, mi_, wp_, pg_, ag_,
- opts___] :=
- Module[{modelF, chisq, tmp, inc, ranges},
- ranges = Map[If[FreeQ[#,DirectedInfinity[_]],#,Take[#,2]]&,
- Transpose[{parameters, start, minr, maxr}]
- ];
- modelF = Function[vars, model];
- chisq = Plus @@ MapThread[
- (#3 (#2 - modelF @@ #1)^2) &,
- {data,response,weights}];
- inc = 1;
- If[prog,
- Last[FindMinimum[
- (If[Head[First[parameters]] =!= Symbol,
- printprogress[inc++,tmp = chisq, parameters]
- ]; tmp),
- Evaluate[Sequence @@ ranges],
- MaxIterations -> mi,
- WorkingPrecision -> wp,
- AccuracyGoal -> ag + 1,
- PrecisionGoal -> pg + 1,
- opts,
- Evaluate[Gradient -> Map[D[chisq,#]&,parameters]]
- ]],
- Last[FindMinimum[chisq,
- Evaluate[Sequence @@ ranges],
- MaxIterations -> mi,
- WorkingPrecision -> wp,
- AccuracyGoal -> ag + 1,
- PrecisionGoal -> pg + 1,
- opts]]
- ]
- ]
-
- nlfit[LevenbergMarquardt, data_, response_, weights_, model_, vars_,
- params_, start_, minr_, maxr_, progress_, maxits_,
- wp_, pg_, ag_, ___] :=
- Module[{ndata = N[data, wp], nresp = N[response, wp],
- nweights = N[weights, wp], guess = N[start, wp],
- nminr = N[minr, wp], nmaxr = N[maxr, wp], lambda = 1/1000,
- maplocs = Transpose[{#,#}]&[Range[Length[params]]],
- alpha, beta, chisq, flag, lastflag, its, deltaguess,
- tmpchi, newchi, newvals, vals, derivs, rngflag, oldguess,
- accgoal = 10^-ag, precgoal = 10^-pg, ovflag = False},
- (* initialize derivatives and function *)
- vals = Apply[Function[vars,model],ndata,{1}];
- derivs = Function[Evaluate[params,
- Map[D[vals,#]&,params]]];
- vals = Function[Evaluate[params, vals]];
- (* initialize other variables *)
- {alpha, beta} =
- calcstep[response, nweights, vals @@ guess, derivs @@ guess,wp];
- chisq = calcchisq[response, nweights, vals @@ guess,wp];
- tmpchi = chisq; newchi = -1; its = 1; oldguess = guess;
- (* main loop *)
- ovflag = Check[While[!((flag = ((0 <= chisq - newchi < accgoal) ||
- (0 <= chisq - newchi < precgoal newchi))) &&
- lastflag) &&
- its <= maxits &&
- (rngflag = And @@ Thread[N[nminr < guess < nmaxr,wp]]),
- lastflag = flag;
- chisq = tmpchi;
- oldguess = guess;
- ++its;
- deltaguess = LinearSolve[
- MapAt[# (1 + lambda)&,alpha,maplocs],
- beta];
- newvals = vals @@ (guess + deltaguess);
- newchi = calcchisq[response, nweights, newvals,wp];
- If[progress,
- printprogress[its - 1, chisq, guess]
- ];
- If[newchi >= chisq,
- lambda = 10 lambda,
- lambda = lambda/10;
- guess = guess + deltaguess;
- tmpchi = newchi;
- {alpha, beta} = calcstep[response, nweights,
- newvals, derivs @@ guess,wp]
- ]
- ],True,General::ovfl];
- (* check convergence of parameters *)
- (* - Warning: this is not a truly appropriate test, but I
- couldn't think of one that worked the way I wanted. May
- revise this after talking to Jerry Keiper... --John N. *)
- If[Not[And @@ Thread[Abs[oldguess - guess] < 1]] && rngflag,
- Message[NonlinearFit::lmpnocon]
- ];
- (* check other error possibilities *)
- If[!rngflag,
- Message[NonlinearFit::lmoutofrng]
- ];
- If[its > maxits,
- Message[NonlinearFit::lmnoconv,maxits]
- ];
- If[TrueQ[ovflag],
- Message[NonlinearFit::lmovfl]
- ];
- (* turn parameters into rules *)
- Apply[Rule, Transpose[{params, guess}],{1}]
- ]
-
- calcchisq[resp_,weight_, vals_,wp_] :=
- N[Plus @@ (weight (resp - vals)^2),wp]
-
- calcstep[resp_, weight_, vals_, derivs_,wp_] :=
- Module[{betatmp,hold},
- betatmp = weight (resp - vals);
- N[{Outer[Dot[weight List @@ #1, List @@ #2]&,
- #, #]&[hold @@ derivs]/.hold->List,
- Map[betatmp . # &, derivs]},wp]
- ]
-
- (* utility to choose starting positions based on a minimum and a maximum
- for the parameter *)
-
- startposns[min_,max_] := {min + (1/3)(max - min), min + (2/3)(max - min)}
-
- (* utility to choose a starting position from those derived with the
- above utility by picking the point with the smallest chi squared. *)
-
- findbeststart[starts_, data_, response_, weights_, model_, vars_, params_] :=
- Module[{chis, chisq, pairs},
- chisq = Function[params,
- Evaluate[Plus @@ MapThread[
- (#3 (#2 - Function[vars, model] @@ #1)^2) &,
- {data,response,weights}]
- ]
- ];
- pairs = Transpose[{starts,chis = N[Apply[chisq, starts,{1}]]}];
- First[First[Select[pairs, #[[2]] == Min[chis] &]]]
- ]
-
-
- (* utility to allow easy formatting of the print progress step... *)
-
- printprogress[iteration_, chisq_, params_] :=
- Print[StringForm[
- "Iteration:`1` ChiSquared:`2` Parameters:`3`",
- iteration, chisq, params]]
-
-
- End[]
-
- EndPackage[]
-