# HG changeset patch # User Jay Belanger # Date 1186199654 0 # Node ID c647b5ba2a467f9acf922ba02d01a547851ffa2d # Parent 4e7d189611f22adce559e0b8a29c074d67ef5b2d New file. diff -r 4e7d189611f2 -r c647b5ba2a46 lisp/calc/calc-nlfit.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/calc/calc-nlfit.el Sat Aug 04 03:54:14 2007 +0000 @@ -0,0 +1,815 @@ +;;; calc-nlfit.el --- nonlinear curve fitting for Calc + +;; Copyright (C) 2007 Free Software Foundation, Inc. + +;; Maintainer: Jay Belanger + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to the +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; This code uses the Levenberg-Marquardt method, as described in +;; _Numerical Analysis_ by H. R. Schwarz, to fit data to +;; nonlinear curves. Currently, the only the following curves are +;; supported: +;; The logistic S curve, y=a/(1+exp(b*(t-c))) +;; Here, y is usually interpreted as the population of some +;; quantity at time t. So we will think of the data as consisting +;; of quantities q0, q1, ..., qn and their respective times +;; t0, t1, ..., tn. + +;; The logistic bell curve, y=A*exp(B*(t-C))/(1+exp(B*(t-C)))^2 +;; Note that this is the derivative of the formula for the S curve. +;; We get A=-a*b, B=b and C=c. Here, y is interpreted as the rate +;; of growth of a population at time t. So we will think of the +;; data as consisting of rates p0, p1, ..., pn and their +;; respective times t0, t1, ..., tn. + +;; The Hubbert Linearization, y/x=A*(1-x/B) +;; Here, y is thought of as the rate of growth of a population +;; and x represents the actual population. This is essentially +;; the differential equation describing the actual population. + +;; The Levenberg-Marquardt method is an iterative process: it takes +;; an initial guess for the parameters and refines them. To get an +;; initial guess for the parameters, we'll use a method described by +;; Luis de Sousa in "Hubbert's Peak Mathematics". The idea is that +;; given quantities Q and the corresponding rates P, they should +;; satisfy P/Q= mQ+a. We can use the parameter a for an +;; approximation for the parameter a in the S curve, and +;; approximations for b and c are found using least squares on the +;; linearization log((a/y)-1) = log(bb) + cc*t of +;; y=a/(1+bb*exp(cc*t)), which is equivalent to the above s curve +;; formula, and then tranlating it to b and c. From this, we can +;; also get approximations for the bell curve parameters. + +;;; Code: + +(require 'calc-arith) + +(defun math-nlfit-least-squares (xdata ydata &optional sdata sigmas) + "Return the parameters A and B for the best least squares fit y=a+bx." + (let* ((n (length xdata)) + (s2data (if sdata + (mapcar 'calcFunc-sqr sdata) + (make-list n 1))) + (S (if sdata 0 n)) + (Sx 0) + (Sy 0) + (Sxx 0) + (Sxy 0) + D) + (while xdata + (let ((x (car xdata)) + (y (car ydata)) + (s (car s2data))) + (setq Sx (math-add Sx (if s (math-div x s) x))) + (setq Sy (math-add Sy (if s (math-div y s) y))) + (setq Sxx (math-add Sxx (if s (math-div (math-mul x x) s) + (math-mul x x)))) + (setq Sxy (math-add Sxy (if s (math-div (math-mul x y) s) + (math-mul x y)))) + (if sdata + (setq S (math-add S (math-div 1 s))))) + (setq xdata (cdr xdata)) + (setq ydata (cdr ydata)) + (setq s2data (cdr s2data))) + (setq D (math-sub (math-mul S Sxx) (math-mul Sx Sx))) + (let ((A (math-div (math-sub (math-mul Sxx Sy) (math-mul Sx Sxy)) D)) + (B (math-div (math-sub (math-mul S Sxy) (math-mul Sx Sy)) D))) + (if sigmas + (let ((C11 (math-div Sxx D)) + (C12 (math-neg (math-div Sx D))) + (C22 (math-div S D))) + (list (list 'sdev A (calcFunc-sqrt C11)) + (list 'sdev B (calcFunc-sqrt C22)) + (list 'vec + (list 'vec C11 C12) + (list 'vec C12 C22)))) + (list A B))))) + +;;; The methods described by de Sousa require the cumulative data qdata +;;; and the rates pdata. We will assume that we are given either +;;; qdata and the corresponding times tdata, or pdata and the corresponding +;;; tdata. The following two functions will find pdata or qdata, +;;; given the other.. + +;;; First, given two lists; one of values q0, q1, ..., qn and one of +;;; corresponding times t0, t1, ..., tn; return a list +;;; p0, p1, ..., pn of the rates of change of the qi with respect to t. +;;; p0 is the right hand derivative (q1 - q0)/(t1 - t0). +;;; pn is the left hand derivative (qn - q(n-1))/(tn - t(n-1)). +;;; The other pis are the averages of the two: +;;; (1/2)((qi - q(i-1))/(ti - t(i-1)) + (q(i+1) - qi)/(t(i+1) - ti)). + +(defun math-nlfit-get-rates-from-cumul (tdata qdata) + (let ((pdata (list + (math-div + (math-sub (nth 1 qdata) + (nth 0 qdata)) + (math-sub (nth 1 tdata) + (nth 0 tdata)))))) + (while (> (length qdata) 2) + (setq pdata + (cons + (math-mul + '(float 5 -1) + (math-add + (math-div + (math-sub (nth 2 qdata) + (nth 1 qdata)) + (math-sub (nth 2 tdata) + (nth 1 tdata))) + (math-div + (math-sub (nth 1 qdata) + (nth 0 qdata)) + (math-sub (nth 1 tdata) + (nth 0 tdata))))) + pdata)) + (setq qdata (cdr qdata))) + (setq pdata + (cons + (math-div + (math-sub (nth 1 qdata) + (nth 0 qdata)) + (math-sub (nth 1 tdata) + (nth 0 tdata))) + pdata)) + (reverse pdata))) + +;;; Next, given two lists -- one of rates p0, p1, ..., pn and one of +;;; corresponding times t0, t1, ..., tn -- and an initial values q0, +;;; return a list q0, q1, ..., qn of the cumulative values. +;;; q0 is the initial value given. +;;; For i>0, qi is computed using the trapezoid rule: +;;; qi = q(i-1) + (1/2)(pi + p(i-1))(ti - t(i-1)) + +(defun math-nlfit-get-cumul-from-rates (tdata pdata q0) + (let* ((qdata (list q0))) + (while (cdr pdata) + (setq qdata + (cons + (math-add (car qdata) + (math-mul + (math-mul + '(float 5 -1) + (math-add (nth 1 pdata) (nth 0 pdata))) + (math-sub (nth 1 tdata) + (nth 0 tdata)))) + qdata)) + (setq pdata (cdr pdata)) + (setq tdata (cdr tdata))) + (reverse qdata))) + +;;; Given the qdata, pdata and tdata, find the parameters +;;; a, b and c that fit q = a/(1+b*exp(c*t)). +;;; a is found using the method described by de Sousa. +;;; b and c are found using least squares on the linearization +;;; log((a/q)-1) = log(b) + c*t +;;; In some cases (where the logistic curve may well be the wrong +;;; model), the computed a will be less than or equal to the maximum +;;; value of q in qdata; in which case the above linearization won't work. +;;; In this case, a will be replaced by a number slightly above +;;; the maximum value of q. + +(defun math-nlfit-find-qmax (qdata pdata tdata) + (let* ((ratios (mapcar* 'math-div pdata qdata)) + (lsdata (math-nlfit-least-squares ratios tdata)) + (qmax (math-max-list (car qdata) (cdr qdata))) + (a (math-neg (math-div (nth 1 lsdata) (nth 0 lsdata))))) + (if (math-lessp a qmax) + (math-add '(float 5 -1) qmax) + a))) + +(defun math-nlfit-find-logistic-parameters (qdata pdata tdata) + (let* ((a (math-nlfit-find-qmax qdata pdata tdata)) + (newqdata + (mapcar (lambda (q) (calcFunc-ln (math-sub (math-div a q) 1))) + qdata)) + (bandc (math-nlfit-least-squares tdata newqdata))) + (list + a + (calcFunc-exp (nth 0 bandc)) + (nth 1 bandc)))) + +;;; Next, given the pdata and tdata, we can find the qdata if we know q0. +;;; We first try to find q0, using the fact that when p takes on its largest +;;; value, q is half of its maximum value. So we'll find the maximum value +;;; of q given various q0, and use bisection to approximate the correct q0. + +;;; First, given pdata and tdata, find what half of qmax would be if q0=0. + +(defun math-nlfit-find-qmaxhalf (pdata tdata) + (let ((pmax (math-max-list (car pdata) (cdr pdata))) + (qmh 0)) + (while (math-lessp (car pdata) pmax) + (setq qmh + (math-add qmh + (math-mul + (math-mul + '(float 5 -1) + (math-add (nth 1 pdata) (nth 0 pdata))) + (math-sub (nth 1 tdata) + (nth 0 tdata))))) + (setq pdata (cdr pdata)) + (setq tdata (cdr tdata))) + qmh)) + +;;; Next, given pdata and tdata, approximate q0. + +(defun math-nlfit-find-q0 (pdata tdata) + (let* ((qhalf (math-nlfit-find-qmaxhalf pdata tdata)) + (q0 (math-mul 2 qhalf)) + (qdata (math-nlfit-get-cumul-from-rates tdata pdata q0))) + (while (math-lessp (math-nlfit-find-qmax + (mapcar + (lambda (q) (math-add q0 q)) + qdata) + pdata tdata) + (math-mul + '(float 5 -1) + (math-add + q0 + qhalf))) + (setq q0 (math-add q0 qhalf))) + (let* ((qmin (math-sub q0 qhalf)) + (qmax q0) + (qt (math-nlfit-find-qmax + (mapcar + (lambda (q) (math-add q0 q)) + qdata) + pdata tdata)) + (i 0)) + (while (< i 10) + (setq q0 (math-mul '(float 5 -1) (math-add qmin qmax))) + (if (math-lessp + (math-nlfit-find-qmax + (mapcar + (lambda (q) (math-add q0 q)) + qdata) + pdata tdata) + (math-mul '(float 5 -1) (math-add qhalf q0))) + (setq qmin q0) + (setq qmax q0)) + (setq i (1+ i))) + (math-mul '(float 5 -1) (math-add qmin qmax))))) + +;;; To improve the approximations to the parameters, we can use +;;; Marquardt method as described in Schwarz's book. + +;;; Small numbers used in the Givens algorithm +(defvar math-nlfit-delta '(float 1 -8)) + +(defvar math-nlfit-epsilon '(float 1 -5)) + +;;; Maximum number of iterations +(defvar math-nlfit-max-its 100) + +;;; Next, we need some functions for dealing with vectors and +;;; matrices. For convenience, we'll work with Emacs lists +;;; as vectors, rather than Calc's vectors. + +(defun math-nlfit-set-elt (vec i x) + (setcar (nthcdr (1- i) vec) x)) + +(defun math-nlfit-get-elt (vec i) + (nth (1- i) vec)) + +(defun math-nlfit-make-matrix (i j) + (let ((row (make-list j 0)) + (mat nil) + (k 0)) + (while (< k i) + (setq mat (cons (copy-list row) mat)) + (setq k (1+ k))) + mat)) + +(defun math-nlfit-set-matx-elt (mat i j x) + (setcar (nthcdr (1- j) (nth (1- i) mat)) x)) + +(defun math-nlfit-get-matx-elt (mat i j) + (nth (1- j) (nth (1- i) mat))) + +;;; For solving the linearized system. +;;; (The Givens method, from Schwarz.) + +(defun math-nlfit-givens (C d) + (let* ((C (copy-tree C)) + (d (copy-tree d)) + (n (length (car C))) + (N (length C)) + (j 1) + (r (make-list N 0)) + (x (make-list N 0)) + w + gamma + sigma + rho) + (while (<= j n) + (let ((i (1+ j))) + (while (<= i N) + (let ((cij (math-nlfit-get-matx-elt C i j)) + (cjj (math-nlfit-get-matx-elt C j j))) + (when (not (math-equal 0 cij)) + (if (math-lessp (calcFunc-abs cjj) + (math-mul math-nlfit-delta (calcFunc-abs cij))) + (setq w (math-neg cij) + gamma 0 + sigma 1 + rho 1) + (setq w (math-mul + (calcFunc-sign cjj) + (calcFunc-sqrt + (math-add + (math-mul cjj cjj) + (math-mul cij cij)))) + gamma (math-div cjj w) + sigma (math-neg (math-div cij w))) + (if (math-lessp (calcFunc-abs sigma) gamma) + (setq rho sigma) + (setq rho (math-div (calcFunc-sign sigma) gamma)))) + (setq cjj w + cij rho) + (math-nlfit-set-matx-elt C j j w) + (math-nlfit-set-matx-elt C i j rho) + (let ((k (1+ j))) + (while (<= k n) + (let* ((cjk (math-nlfit-get-matx-elt C j k)) + (cik (math-nlfit-get-matx-elt C i k)) + (h (math-sub + (math-mul gamma cjk) (math-mul sigma cik)))) + (setq cik (math-add + (math-mul sigma cjk) + (math-mul gamma cik))) + (setq cjk h) + (math-nlfit-set-matx-elt C i k cik) + (math-nlfit-set-matx-elt C j k cjk) + (setq k (1+ k))))) + (let* ((di (math-nlfit-get-elt d i)) + (dj (math-nlfit-get-elt d j)) + (h (math-sub + (math-mul gamma dj) + (math-mul sigma di)))) + (setq di (math-add + (math-mul sigma dj) + (math-mul gamma di))) + (setq dj h) + (math-nlfit-set-elt d i di) + (math-nlfit-set-elt d j dj)))) + (setq i (1+ i)))) + (setq j (1+ j))) + (let ((i n)) + (while (>= i 1) + (math-nlfit-set-elt r i 0) + (setq s (math-nlfit-get-elt d i)) + (let ((k (1+ i))) + (while (<= k n) + (setq s (math-add s (math-mul (math-nlfit-get-matx-elt C i k) + (math-nlfit-get-elt x k)))) + (setq k (1+ k)))) + (math-nlfit-set-elt x i + (math-neg + (math-div s + (math-nlfit-get-matx-elt C i i)))) + (setq i (1- i)))) + (let ((i (1+ n))) + (while (<= i N) + (math-nlfit-set-elt r i (math-nlfit-get-elt d i)) + (setq i (1+ i)))) + (let ((j n)) + (while (>= j 1) + (let ((i N)) + (while (>= i (1+ j)) + (setq rho (math-nlfit-get-matx-elt C i j)) + (if (math-equal rho 1) + (setq gamma 0 + sigma 1) + (if (math-lessp (calcFunc-abs rho) 1) + (setq sigma rho + gamma (calcFunc-sqrt + (math-sub 1 (math-mul sigma sigma)))) + (setq gamma (math-div 1 (calcFunc-abs rho)) + sigma (math-mul (calcFunc-sign rho) + (calcFunc-sqrt + (math-sub 1 (math-mul gamma gamma))))))) + (let ((ri (math-nlfit-get-elt r i)) + (rj (math-nlfit-get-elt r j))) + (setq h (math-add (math-mul gamma rj) + (math-mul sigma ri))) + (setq ri (math-sub + (math-mul gamma ri) + (math-mul sigma rj))) + (setq rj h) + (math-nlfit-set-elt r i ri) + (math-nlfit-set-elt r j rj)) + (setq i (1- i)))) + (setq j (1- j)))) + + x)) + +(defun math-nlfit-jacobian (grad xlist parms &optional slist) + (let ((j nil)) + (while xlist + (let ((row (apply grad (car xlist) parms))) + (setq j + (cons + (if slist + (mapcar (lambda (x) (math-div x (car slist))) row) + row) + j))) + (setq slist (cdr slist)) + (setq xlist (cdr xlist))) + (reverse j))) + +(defun math-nlfit-make-ident (l n) + (let ((m (math-nlfit-make-matrix n n)) + (i 1)) + (while (<= i n) + (math-nlfit-set-matx-elt m i i l) + (setq i (1+ i))) + m)) + +(defun math-nlfit-chi-sq (xlist ylist parms fn &optional slist) + (let ((cs 0)) + (while xlist + (let ((c + (math-sub + (apply fn (car xlist) parms) + (car ylist)))) + (if slist + (setq c (math-div c (car slist)))) + (setq cs + (math-add cs + (math-mul c c)))) + (setq xlist (cdr xlist)) + (setq ylist (cdr ylist)) + (setq slist (cdr slist))) + cs)) + +(defun math-nlfit-init-lambda (C) + (let ((l 0) + (n (length (car C))) + (N (length C))) + (while C + (let ((row (car C))) + (while row + (setq l (math-add l (math-mul (car row) (car row)))) + (setq row (cdr row)))) + (setq C (cdr C))) + (calcFunc-sqrt (math-div l (math-mul n N))))) + +(defun math-nlfit-make-Ctilda (C l) + (let* ((n (length (car C))) + (bot (math-nlfit-make-ident l n))) + (append C bot))) + +(defun math-nlfit-make-d (fn xdata ydata parms &optional sdata) + (let ((d nil)) + (while xdata + (setq d (cons + (let ((dd (math-sub (apply fn (car xdata) parms) + (car ydata)))) + (if sdata (math-div dd (car sdata)) dd)) + d)) + (setq xdata (cdr xdata)) + (setq ydata (cdr ydata)) + (setq sdata (cdr sdata))) + (reverse d))) + +(defun math-nlfit-make-dtilda (d n) + (append d (make-list n 0))) + +(defun math-nlfit-fit (xlist ylist parms fn grad &optional slist) + (let* + ((C (math-nlfit-jacobian grad xlist parms slist)) + (d (math-nlfit-make-d fn xlist ylist parms slist)) + (chisq (math-nlfit-chi-sq xlist ylist parms fn slist)) + (lambda (math-nlfit-init-lambda C)) + (really-done nil) + (iters 0)) + (while (and + (not really-done) + (< iters math-nlfit-max-its)) + (setq iters (1+ iters)) + (let ((done nil)) + (while (not done) + (let* ((Ctilda (math-nlfit-make-Ctilda C lambda)) + (dtilda (math-nlfit-make-dtilda d (length (car C)))) + (zeta (math-nlfit-givens Ctilda dtilda)) + (newparms (mapcar* 'math-add (copy-tree parms) zeta)) + (newchisq (math-nlfit-chi-sq xlist ylist newparms fn slist))) + (if (math-lessp newchisq chisq) + (progn + (if (math-lessp + (math-div + (math-sub chisq newchisq) newchisq) math-nlfit-epsilon) + (setq really-done t)) + (setq lambda (math-div lambda 10)) + (setq chisq newchisq) + (setq parms newparms) + (setq done t)) + (setq lambda (math-mul lambda 10))))) + (setq C (math-nlfit-jacobian grad xlist parms slist)) + (setq d (math-nlfit-make-d fn xlist ylist parms slist)))) + (list chisq parms))) + +;;; The functions that describe our models, and their gradients. + +(defun math-nlfit-s-logistic-fn (x a b c) + (math-div a (math-add 1 (math-mul b (calcFunc-exp (math-mul c x)))))) + +(defun math-nlfit-s-logistic-grad (x a b c) + (let* ((ep (calcFunc-exp (math-mul c x))) + (d (math-add 1 (math-mul b ep))) + (d2 (math-mul d d))) + (list + (math-div 1 d) + (math-neg (math-div (math-mul a ep) d2)) + (math-neg (math-div (math-mul a (math-mul b (math-mul x ep))) d2))))) + +(defun math-nlfit-b-logistic-fn (x a c d) + (let ((ex (calcFunc-exp (math-mul c (math-sub x d))))) + (math-div + (math-mul a ex) + (math-sqr + (math-add + 1 ex))))) + +(defun math-nlfit-b-logistic-grad (x a c d) + (let* ((ex (calcFunc-exp (math-mul c (math-sub x d)))) + (ex1 (math-add 1 ex)) + (xd (math-sub x d))) + (list + (math-div + ex + (math-sqr ex1)) + (math-sub + (math-div + (math-mul a (math-mul xd ex)) + (math-sqr ex1)) + (math-div + (math-mul 2 (math-mul a (math-mul xd (math-sqr ex)))) + (math-pow ex1 3))) + (math-sub + (math-div + (math-mul 2 (math-mul a (math-mul c (math-sqr ex)))) + (math-pow ex1 3)) + (math-div + (math-mul a (math-mul c ex)) + (math-sqr ex1)))))) + +;;; Functions to get the final covariance matrix and the sdevs + +(defun math-nlfit-find-covar (grad xlist pparms) + (let ((j nil)) + (while xlist + (setq j (cons (cons 'vec (apply grad (car xlist) pparms)) j)) + (setq xlist (cdr xlist))) + (setq j (cons 'vec (reverse j))) + (setq j + (math-mul + (calcFunc-trn j) j)) + (calcFunc-inv j))) + +(defun math-nlfit-get-sigmas (grad xlist pparms chisq) + (let* ((sgs nil) + (covar (math-nlfit-find-covar grad xlist pparms)) + (n (1- (length covar))) + (N (length xlist)) + (i 1)) + (when (> N n) + (while (<= i n) + (setq sgs (cons (calcFunc-sqrt (nth i (nth i covar))) sgs)) + (setq i (1+ i))) + (setq sgs (reverse sgs))) + (list sgs covar))) + +;;; Now the Calc functions + +(defun math-nlfit-s-logistic-params (xdata ydata) + (let ((pdata (math-nlfit-get-rates-from-cumul xdata ydata))) + (math-nlfit-find-logistic-parameters ydata pdata xdata))) + +(defun math-nlfit-b-logistic-params (xdata ydata) + (let* ((q0 (math-nlfit-find-q0 ydata xdata)) + (qdata (math-nlfit-get-cumul-from-rates xdata ydata q0)) + (abc (math-nlfit-find-logistic-parameters qdata ydata xdata)) + (B (nth 1 abc)) + (C (nth 2 abc)) + (A (math-neg + (math-mul + (nth 0 abc) + (math-mul B C)))) + (D (math-neg (math-div (calcFunc-ln B) C))) + (A (math-div A B))) + (list A C D))) + +;;; Some functions to turn the parameter lists and variables +;;; into the appropriate functions. + +(defun math-nlfit-s-logistic-solnexpr (pms var) + (let ((a (nth 0 pms)) + (b (nth 1 pms)) + (c (nth 2 pms))) + (list '/ a + (list '+ + 1 + (list '* + b + (calcFunc-exp + (list '* + c + var))))))) + +(defun math-nlfit-b-logistic-solnexpr (pms var) + (let ((a (nth 0 pms)) + (c (nth 1 pms)) + (d (nth 2 pms))) + (list '/ + (list '* + a + (calcFunc-exp + (list '* + c + (list '- var d)))) + (list '^ + (list '+ + 1 + (calcFunc-exp + (list '* + c + (list '- var d)))) + 2)))) + +(defun math-nlfit-enter-result (n prefix vals) + (setq calc-aborted-prefix prefix) + (calc-pop-push-record-list n prefix vals) + (calc-handle-whys)) + +(defun math-nlfit-fit-curve (fn grad solnexpr initparms &optional sdv) + (calc-slow-wrapper + (let* ((sdevv (or (eq sdv 'calcFunc-efit) (eq sdv 'calcFunc-xfit))) + (calc-display-working-message nil) + (data (calc-top 1)) + (xdata (cdr (car (cdr data)))) + (ydata (cdr (car (cdr (cdr data))))) + (sdata (if (math-contains-sdev-p ydata) + (mapcar (lambda (x) (math-get-sdev x t)) ydata) + nil)) + (ydata (mapcar (lambda (x) (math-get-value x)) ydata)) + (zzz (progn (setq j1 xdata j2 ydata j3 sdata) 1)) + + (calc-curve-varnames nil) + (calc-curve-coefnames nil) + (calc-curve-nvars 1) + (fitvars (calc-get-fit-variables 1 3)) + (var (nth 1 calc-curve-varnames)) + (parms (cdr calc-curve-coefnames)) + (parmguess + (funcall initparms xdata ydata)) + (fit (math-nlfit-fit xdata ydata parmguess fn grad sdata)) + (finalparms (nth 1 fit)) + (sigmacovar + (if sdevv + (math-nlfit-get-sigmas grad xdata finalparms (nth 0 fit)))) + (sigmas + (if sdevv + (nth 0 sigmacovar))) + (finalparms + (if sigmas + (mapcar* (lambda (x y) (list 'sdev x y)) finalparms sigmas) + finalparms)) + (soln (funcall solnexpr finalparms var))) + (let ((calc-fit-to-trail t) + (traillist nil)) + (while parms + (setq traillist (cons (list 'calcFunc-eq (car parms) (car finalparms)) + traillist)) + (setq finalparms (cdr finalparms)) + (setq parms (cdr parms))) + (setq traillist (calc-normalize (cons 'vec (nreverse traillist)))) + (cond ((eq sdv 'calcFunc-efit) + (math-nlfit-enter-result 1 "efit" soln)) + ((eq sdv 'calcFunc-xfit) + (let (sln) + (setq sln + (list 'vec + soln + traillist + (nth 1 sigmacovar) + '(vec) + (nth 0 fit) + (let ((n (length xdata)) + (m (length finalparms))) + (if (and sdata (> n m)) + (calcFunc-utpc (nth 0 fit) + (- n m)) + '(var nan var-nan))))) + (math-nlfit-enter-result 1 "xfit" sln))) + (t + (math-nlfit-enter-result 1 "fit" soln))) + (calc-record traillist "parm"))))) + +(defun calc-fit-s-shaped-logistic-curve (arg) + (interactive "P") + (math-nlfit-fit-curve 'math-nlfit-s-logistic-fn + 'math-nlfit-s-logistic-grad + 'math-nlfit-s-logistic-solnexpr + 'math-nlfit-s-logistic-params + arg)) + +(defun calc-fit-bell-shaped-logistic-curve (arg) + (interactive "P") + (math-nlfit-fit-curve 'math-nlfit-b-logistic-fn + 'math-nlfit-b-logistic-grad + 'math-nlfit-b-logistic-solnexpr + 'math-nlfit-b-logistic-params + arg)) + +(defun calc-fit-hubbert-linear-curve (&optional sdv) + (calc-slow-wrapper + (let* ((sdevv (or (eq sdv 'calcFunc-efit) (eq sdv 'calcFunc-xfit))) + (calc-display-working-message nil) + (data (calc-top 1)) + (qdata (cdr (car (cdr data)))) + (pdata (cdr (car (cdr (cdr data))))) + (sdata (if (math-contains-sdev-p pdata) + (mapcar (lambda (x) (math-get-sdev x t)) pdata) + nil)) + (pdata (mapcar (lambda (x) (math-get-value x)) pdata)) + (poverqdata (mapcar* 'math-div pdata qdata)) + (parmvals (math-nlfit-least-squares qdata poverqdata sdata sdevv)) + (finalparms (list (nth 0 parmvals) + (math-neg + (math-div (nth 0 parmvals) + (nth 1 parmvals))))) + (calc-curve-varnames nil) + (calc-curve-coefnames nil) + (calc-curve-nvars 1) + (fitvars (calc-get-fit-variables 1 2)) + (var (nth 1 calc-curve-varnames)) + (parms (cdr calc-curve-coefnames)) + (soln (list '* (nth 0 finalparms) + (list '- 1 + (list '/ var (nth 1 finalparms)))))) + (let ((calc-fit-to-trail t) + (traillist nil)) + (setq traillist + (list 'vec + (list 'calcFunc-eq (nth 0 parms) (nth 0 finalparms)) + (list 'calcFunc-eq (nth 1 parms) (nth 1 finalparms)))) + (cond ((eq sdv 'calcFunc-efit) + (math-nlfit-enter-result 1 "efit" soln)) + ((eq sdv 'calcFunc-xfit) + (let (sln + (chisq + (math-nlfit-chi-sq + qdata poverqdata + (list (nth 1 (nth 0 finalparms)) + (nth 1 (nth 1 finalparms))) + (lambda (x a b) + (math-mul a + (math-sub + 1 + (math-div x b)))) + sdata))) + (setq sln + (list 'vec + soln + traillist + (nth 2 parmvals) + (list + 'vec + '(calcFunc-fitdummy 1) + (list 'calcFunc-neg + (list '/ + '(calcFunc-fitdummy 1) + '(calcFunc-fitdummy 2)))) + chisq + (let ((n (length qdata))) + (if (and sdata (> n 2)) + (calcFunc-utpc + chisq + (- n 2)) + '(var nan var-nan))))) + (math-nlfit-enter-result 1 "xfit" sln))) + (t + (math-nlfit-enter-result 1 "fit" soln))) + (calc-record traillist "parm"))))) + +(provide 'calc-nlfit)