diff options
| author | Jay Belanger | 2007-08-04 03:54:14 +0000 |
|---|---|---|
| committer | Jay Belanger | 2007-08-04 03:54:14 +0000 |
| commit | 08626df898cb605f5f1edea67dd339654e944919 (patch) | |
| tree | 1f547eca97c752467ddacd51a0436463919c05dd | |
| parent | b07251cc945ee7e78d0ea8a8f8bcf5040faeaaf9 (diff) | |
| download | emacs-08626df898cb605f5f1edea67dd339654e944919.tar.gz emacs-08626df898cb605f5f1edea67dd339654e944919.zip | |
New file.
| -rw-r--r-- | lisp/calc/calc-nlfit.el | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/lisp/calc/calc-nlfit.el b/lisp/calc/calc-nlfit.el new file mode 100644 index 00000000000..9a982aa7352 --- /dev/null +++ b/lisp/calc/calc-nlfit.el | |||
| @@ -0,0 +1,815 @@ | |||
| 1 | ;;; calc-nlfit.el --- nonlinear curve fitting for Calc | ||
| 2 | |||
| 3 | ;; Copyright (C) 2007 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Maintainer: Jay Belanger <jay.p.belanger@gmail.com> | ||
| 6 | |||
| 7 | ;; This file is part of GNU Emacs. | ||
| 8 | |||
| 9 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 10 | ;; it under the terms of the GNU General Public License as published by | ||
| 11 | ;; the Free Software Foundation; either version 3, or (at your option) | ||
| 12 | ;; any later version. | ||
| 13 | |||
| 14 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 17 | ;; GNU General Public License for more details. | ||
| 18 | |||
| 19 | ;; You should have received a copy of the GNU General Public License | ||
| 20 | ;; along with GNU Emacs; see the file COPYING. If not, write to the | ||
| 21 | ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
| 22 | ;; Boston, MA 02110-1301, USA. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | |||
| 26 | ;; This code uses the Levenberg-Marquardt method, as described in | ||
| 27 | ;; _Numerical Analysis_ by H. R. Schwarz, to fit data to | ||
| 28 | ;; nonlinear curves. Currently, the only the following curves are | ||
| 29 | ;; supported: | ||
| 30 | ;; The logistic S curve, y=a/(1+exp(b*(t-c))) | ||
| 31 | ;; Here, y is usually interpreted as the population of some | ||
| 32 | ;; quantity at time t. So we will think of the data as consisting | ||
| 33 | ;; of quantities q0, q1, ..., qn and their respective times | ||
| 34 | ;; t0, t1, ..., tn. | ||
| 35 | |||
| 36 | ;; The logistic bell curve, y=A*exp(B*(t-C))/(1+exp(B*(t-C)))^2 | ||
| 37 | ;; Note that this is the derivative of the formula for the S curve. | ||
| 38 | ;; We get A=-a*b, B=b and C=c. Here, y is interpreted as the rate | ||
| 39 | ;; of growth of a population at time t. So we will think of the | ||
| 40 | ;; data as consisting of rates p0, p1, ..., pn and their | ||
| 41 | ;; respective times t0, t1, ..., tn. | ||
| 42 | |||
| 43 | ;; The Hubbert Linearization, y/x=A*(1-x/B) | ||
| 44 | ;; Here, y is thought of as the rate of growth of a population | ||
| 45 | ;; and x represents the actual population. This is essentially | ||
| 46 | ;; the differential equation describing the actual population. | ||
| 47 | |||
| 48 | ;; The Levenberg-Marquardt method is an iterative process: it takes | ||
| 49 | ;; an initial guess for the parameters and refines them. To get an | ||
| 50 | ;; initial guess for the parameters, we'll use a method described by | ||
| 51 | ;; Luis de Sousa in "Hubbert's Peak Mathematics". The idea is that | ||
| 52 | ;; given quantities Q and the corresponding rates P, they should | ||
| 53 | ;; satisfy P/Q= mQ+a. We can use the parameter a for an | ||
| 54 | ;; approximation for the parameter a in the S curve, and | ||
| 55 | ;; approximations for b and c are found using least squares on the | ||
| 56 | ;; linearization log((a/y)-1) = log(bb) + cc*t of | ||
| 57 | ;; y=a/(1+bb*exp(cc*t)), which is equivalent to the above s curve | ||
| 58 | ;; formula, and then tranlating it to b and c. From this, we can | ||
| 59 | ;; also get approximations for the bell curve parameters. | ||
| 60 | |||
| 61 | ;;; Code: | ||
| 62 | |||
| 63 | (require 'calc-arith) | ||
| 64 | |||
| 65 | (defun math-nlfit-least-squares (xdata ydata &optional sdata sigmas) | ||
| 66 | "Return the parameters A and B for the best least squares fit y=a+bx." | ||
| 67 | (let* ((n (length xdata)) | ||
| 68 | (s2data (if sdata | ||
| 69 | (mapcar 'calcFunc-sqr sdata) | ||
| 70 | (make-list n 1))) | ||
| 71 | (S (if sdata 0 n)) | ||
| 72 | (Sx 0) | ||
| 73 | (Sy 0) | ||
| 74 | (Sxx 0) | ||
| 75 | (Sxy 0) | ||
| 76 | D) | ||
| 77 | (while xdata | ||
| 78 | (let ((x (car xdata)) | ||
| 79 | (y (car ydata)) | ||
| 80 | (s (car s2data))) | ||
| 81 | (setq Sx (math-add Sx (if s (math-div x s) x))) | ||
| 82 | (setq Sy (math-add Sy (if s (math-div y s) y))) | ||
| 83 | (setq Sxx (math-add Sxx (if s (math-div (math-mul x x) s) | ||
| 84 | (math-mul x x)))) | ||
| 85 | (setq Sxy (math-add Sxy (if s (math-div (math-mul x y) s) | ||
| 86 | (math-mul x y)))) | ||
| 87 | (if sdata | ||
| 88 | (setq S (math-add S (math-div 1 s))))) | ||
| 89 | (setq xdata (cdr xdata)) | ||
| 90 | (setq ydata (cdr ydata)) | ||
| 91 | (setq s2data (cdr s2data))) | ||
| 92 | (setq D (math-sub (math-mul S Sxx) (math-mul Sx Sx))) | ||
| 93 | (let ((A (math-div (math-sub (math-mul Sxx Sy) (math-mul Sx Sxy)) D)) | ||
| 94 | (B (math-div (math-sub (math-mul S Sxy) (math-mul Sx Sy)) D))) | ||
| 95 | (if sigmas | ||
| 96 | (let ((C11 (math-div Sxx D)) | ||
| 97 | (C12 (math-neg (math-div Sx D))) | ||
| 98 | (C22 (math-div S D))) | ||
| 99 | (list (list 'sdev A (calcFunc-sqrt C11)) | ||
| 100 | (list 'sdev B (calcFunc-sqrt C22)) | ||
| 101 | (list 'vec | ||
| 102 | (list 'vec C11 C12) | ||
| 103 | (list 'vec C12 C22)))) | ||
| 104 | (list A B))))) | ||
| 105 | |||
| 106 | ;;; The methods described by de Sousa require the cumulative data qdata | ||
| 107 | ;;; and the rates pdata. We will assume that we are given either | ||
| 108 | ;;; qdata and the corresponding times tdata, or pdata and the corresponding | ||
| 109 | ;;; tdata. The following two functions will find pdata or qdata, | ||
| 110 | ;;; given the other.. | ||
| 111 | |||
| 112 | ;;; First, given two lists; one of values q0, q1, ..., qn and one of | ||
| 113 | ;;; corresponding times t0, t1, ..., tn; return a list | ||
| 114 | ;;; p0, p1, ..., pn of the rates of change of the qi with respect to t. | ||
| 115 | ;;; p0 is the right hand derivative (q1 - q0)/(t1 - t0). | ||
| 116 | ;;; pn is the left hand derivative (qn - q(n-1))/(tn - t(n-1)). | ||
| 117 | ;;; The other pis are the averages of the two: | ||
| 118 | ;;; (1/2)((qi - q(i-1))/(ti - t(i-1)) + (q(i+1) - qi)/(t(i+1) - ti)). | ||
| 119 | |||
| 120 | (defun math-nlfit-get-rates-from-cumul (tdata qdata) | ||
| 121 | (let ((pdata (list | ||
| 122 | (math-div | ||
| 123 | (math-sub (nth 1 qdata) | ||
| 124 | (nth 0 qdata)) | ||
| 125 | (math-sub (nth 1 tdata) | ||
| 126 | (nth 0 tdata)))))) | ||
| 127 | (while (> (length qdata) 2) | ||
| 128 | (setq pdata | ||
| 129 | (cons | ||
| 130 | (math-mul | ||
| 131 | '(float 5 -1) | ||
| 132 | (math-add | ||
| 133 | (math-div | ||
| 134 | (math-sub (nth 2 qdata) | ||
| 135 | (nth 1 qdata)) | ||
| 136 | (math-sub (nth 2 tdata) | ||
| 137 | (nth 1 tdata))) | ||
| 138 | (math-div | ||
| 139 | (math-sub (nth 1 qdata) | ||
| 140 | (nth 0 qdata)) | ||
| 141 | (math-sub (nth 1 tdata) | ||
| 142 | (nth 0 tdata))))) | ||
| 143 | pdata)) | ||
| 144 | (setq qdata (cdr qdata))) | ||
| 145 | (setq pdata | ||
| 146 | (cons | ||
| 147 | (math-div | ||
| 148 | (math-sub (nth 1 qdata) | ||
| 149 | (nth 0 qdata)) | ||
| 150 | (math-sub (nth 1 tdata) | ||
| 151 | (nth 0 tdata))) | ||
| 152 | pdata)) | ||
| 153 | (reverse pdata))) | ||
| 154 | |||
| 155 | ;;; Next, given two lists -- one of rates p0, p1, ..., pn and one of | ||
| 156 | ;;; corresponding times t0, t1, ..., tn -- and an initial values q0, | ||
| 157 | ;;; return a list q0, q1, ..., qn of the cumulative values. | ||
| 158 | ;;; q0 is the initial value given. | ||
| 159 | ;;; For i>0, qi is computed using the trapezoid rule: | ||
| 160 | ;;; qi = q(i-1) + (1/2)(pi + p(i-1))(ti - t(i-1)) | ||
| 161 | |||
| 162 | (defun math-nlfit-get-cumul-from-rates (tdata pdata q0) | ||
| 163 | (let* ((qdata (list q0))) | ||
| 164 | (while (cdr pdata) | ||
| 165 | (setq qdata | ||
| 166 | (cons | ||
| 167 | (math-add (car qdata) | ||
| 168 | (math-mul | ||
| 169 | (math-mul | ||
| 170 | '(float 5 -1) | ||
| 171 | (math-add (nth 1 pdata) (nth 0 pdata))) | ||
| 172 | (math-sub (nth 1 tdata) | ||
| 173 | (nth 0 tdata)))) | ||
| 174 | qdata)) | ||
| 175 | (setq pdata (cdr pdata)) | ||
| 176 | (setq tdata (cdr tdata))) | ||
| 177 | (reverse qdata))) | ||
| 178 | |||
| 179 | ;;; Given the qdata, pdata and tdata, find the parameters | ||
| 180 | ;;; a, b and c that fit q = a/(1+b*exp(c*t)). | ||
| 181 | ;;; a is found using the method described by de Sousa. | ||
| 182 | ;;; b and c are found using least squares on the linearization | ||
| 183 | ;;; log((a/q)-1) = log(b) + c*t | ||
| 184 | ;;; In some cases (where the logistic curve may well be the wrong | ||
| 185 | ;;; model), the computed a will be less than or equal to the maximum | ||
| 186 | ;;; value of q in qdata; in which case the above linearization won't work. | ||
| 187 | ;;; In this case, a will be replaced by a number slightly above | ||
| 188 | ;;; the maximum value of q. | ||
| 189 | |||
| 190 | (defun math-nlfit-find-qmax (qdata pdata tdata) | ||
| 191 | (let* ((ratios (mapcar* 'math-div pdata qdata)) | ||
| 192 | (lsdata (math-nlfit-least-squares ratios tdata)) | ||
| 193 | (qmax (math-max-list (car qdata) (cdr qdata))) | ||
| 194 | (a (math-neg (math-div (nth 1 lsdata) (nth 0 lsdata))))) | ||
| 195 | (if (math-lessp a qmax) | ||
| 196 | (math-add '(float 5 -1) qmax) | ||
| 197 | a))) | ||
| 198 | |||
| 199 | (defun math-nlfit-find-logistic-parameters (qdata pdata tdata) | ||
| 200 | (let* ((a (math-nlfit-find-qmax qdata pdata tdata)) | ||
| 201 | (newqdata | ||
| 202 | (mapcar (lambda (q) (calcFunc-ln (math-sub (math-div a q) 1))) | ||
| 203 | qdata)) | ||
| 204 | (bandc (math-nlfit-least-squares tdata newqdata))) | ||
| 205 | (list | ||
| 206 | a | ||
| 207 | (calcFunc-exp (nth 0 bandc)) | ||
| 208 | (nth 1 bandc)))) | ||
| 209 | |||
| 210 | ;;; Next, given the pdata and tdata, we can find the qdata if we know q0. | ||
| 211 | ;;; We first try to find q0, using the fact that when p takes on its largest | ||
| 212 | ;;; value, q is half of its maximum value. So we'll find the maximum value | ||
| 213 | ;;; of q given various q0, and use bisection to approximate the correct q0. | ||
| 214 | |||
| 215 | ;;; First, given pdata and tdata, find what half of qmax would be if q0=0. | ||
| 216 | |||
| 217 | (defun math-nlfit-find-qmaxhalf (pdata tdata) | ||
| 218 | (let ((pmax (math-max-list (car pdata) (cdr pdata))) | ||
| 219 | (qmh 0)) | ||
| 220 | (while (math-lessp (car pdata) pmax) | ||
| 221 | (setq qmh | ||
| 222 | (math-add qmh | ||
| 223 | (math-mul | ||
| 224 | (math-mul | ||
| 225 | '(float 5 -1) | ||
| 226 | (math-add (nth 1 pdata) (nth 0 pdata))) | ||
| 227 | (math-sub (nth 1 tdata) | ||
| 228 | (nth 0 tdata))))) | ||
| 229 | (setq pdata (cdr pdata)) | ||
| 230 | (setq tdata (cdr tdata))) | ||
| 231 | qmh)) | ||
| 232 | |||
| 233 | ;;; Next, given pdata and tdata, approximate q0. | ||
| 234 | |||
| 235 | (defun math-nlfit-find-q0 (pdata tdata) | ||
| 236 | (let* ((qhalf (math-nlfit-find-qmaxhalf pdata tdata)) | ||
| 237 | (q0 (math-mul 2 qhalf)) | ||
| 238 | (qdata (math-nlfit-get-cumul-from-rates tdata pdata q0))) | ||
| 239 | (while (math-lessp (math-nlfit-find-qmax | ||
| 240 | (mapcar | ||
| 241 | (lambda (q) (math-add q0 q)) | ||
| 242 | qdata) | ||
| 243 | pdata tdata) | ||
| 244 | (math-mul | ||
| 245 | '(float 5 -1) | ||
| 246 | (math-add | ||
| 247 | q0 | ||
| 248 | qhalf))) | ||
| 249 | (setq q0 (math-add q0 qhalf))) | ||
| 250 | (let* ((qmin (math-sub q0 qhalf)) | ||
| 251 | (qmax q0) | ||
| 252 | (qt (math-nlfit-find-qmax | ||
| 253 | (mapcar | ||
| 254 | (lambda (q) (math-add q0 q)) | ||
| 255 | qdata) | ||
| 256 | pdata tdata)) | ||
| 257 | (i 0)) | ||
| 258 | (while (< i 10) | ||
| 259 | (setq q0 (math-mul '(float 5 -1) (math-add qmin qmax))) | ||
| 260 | (if (math-lessp | ||
| 261 | (math-nlfit-find-qmax | ||
| 262 | (mapcar | ||
| 263 | (lambda (q) (math-add q0 q)) | ||
| 264 | qdata) | ||
| 265 | pdata tdata) | ||
| 266 | (math-mul '(float 5 -1) (math-add qhalf q0))) | ||
| 267 | (setq qmin q0) | ||
| 268 | (setq qmax q0)) | ||
| 269 | (setq i (1+ i))) | ||
| 270 | (math-mul '(float 5 -1) (math-add qmin qmax))))) | ||
| 271 | |||
| 272 | ;;; To improve the approximations to the parameters, we can use | ||
| 273 | ;;; Marquardt method as described in Schwarz's book. | ||
| 274 | |||
| 275 | ;;; Small numbers used in the Givens algorithm | ||
| 276 | (defvar math-nlfit-delta '(float 1 -8)) | ||
| 277 | |||
| 278 | (defvar math-nlfit-epsilon '(float 1 -5)) | ||
| 279 | |||
| 280 | ;;; Maximum number of iterations | ||
| 281 | (defvar math-nlfit-max-its 100) | ||
| 282 | |||
| 283 | ;;; Next, we need some functions for dealing with vectors and | ||
| 284 | ;;; matrices. For convenience, we'll work with Emacs lists | ||
| 285 | ;;; as vectors, rather than Calc's vectors. | ||
| 286 | |||
| 287 | (defun math-nlfit-set-elt (vec i x) | ||
| 288 | (setcar (nthcdr (1- i) vec) x)) | ||
| 289 | |||
| 290 | (defun math-nlfit-get-elt (vec i) | ||
| 291 | (nth (1- i) vec)) | ||
| 292 | |||
| 293 | (defun math-nlfit-make-matrix (i j) | ||
| 294 | (let ((row (make-list j 0)) | ||
| 295 | (mat nil) | ||
| 296 | (k 0)) | ||
| 297 | (while (< k i) | ||
| 298 | (setq mat (cons (copy-list row) mat)) | ||
| 299 | (setq k (1+ k))) | ||
| 300 | mat)) | ||
| 301 | |||
| 302 | (defun math-nlfit-set-matx-elt (mat i j x) | ||
| 303 | (setcar (nthcdr (1- j) (nth (1- i) mat)) x)) | ||
| 304 | |||
| 305 | (defun math-nlfit-get-matx-elt (mat i j) | ||
| 306 | (nth (1- j) (nth (1- i) mat))) | ||
| 307 | |||
| 308 | ;;; For solving the linearized system. | ||
| 309 | ;;; (The Givens method, from Schwarz.) | ||
| 310 | |||
| 311 | (defun math-nlfit-givens (C d) | ||
| 312 | (let* ((C (copy-tree C)) | ||
| 313 | (d (copy-tree d)) | ||
| 314 | (n (length (car C))) | ||
| 315 | (N (length C)) | ||
| 316 | (j 1) | ||
| 317 | (r (make-list N 0)) | ||
| 318 | (x (make-list N 0)) | ||
| 319 | w | ||
| 320 | gamma | ||
| 321 | sigma | ||
| 322 | rho) | ||
| 323 | (while (<= j n) | ||
| 324 | (let ((i (1+ j))) | ||
| 325 | (while (<= i N) | ||
| 326 | (let ((cij (math-nlfit-get-matx-elt C i j)) | ||
| 327 | (cjj (math-nlfit-get-matx-elt C j j))) | ||
| 328 | (when (not (math-equal 0 cij)) | ||
| 329 | (if (math-lessp (calcFunc-abs cjj) | ||
| 330 | (math-mul math-nlfit-delta (calcFunc-abs cij))) | ||
| 331 | (setq w (math-neg cij) | ||
| 332 | gamma 0 | ||
| 333 | sigma 1 | ||
| 334 | rho 1) | ||
| 335 | (setq w (math-mul | ||
| 336 | (calcFunc-sign cjj) | ||
| 337 | (calcFunc-sqrt | ||
| 338 | (math-add | ||
| 339 | (math-mul cjj cjj) | ||
| 340 | (math-mul cij cij)))) | ||
| 341 | gamma (math-div cjj w) | ||
| 342 | sigma (math-neg (math-div cij w))) | ||
| 343 | (if (math-lessp (calcFunc-abs sigma) gamma) | ||
| 344 | (setq rho sigma) | ||
| 345 | (setq rho (math-div (calcFunc-sign sigma) gamma)))) | ||
| 346 | (setq cjj w | ||
| 347 | cij rho) | ||
| 348 | (math-nlfit-set-matx-elt C j j w) | ||
| 349 | (math-nlfit-set-matx-elt C i j rho) | ||
| 350 | (let ((k (1+ j))) | ||
| 351 | (while (<= k n) | ||
| 352 | (let* ((cjk (math-nlfit-get-matx-elt C j k)) | ||
| 353 | (cik (math-nlfit-get-matx-elt C i k)) | ||
| 354 | (h (math-sub | ||
| 355 | (math-mul gamma cjk) (math-mul sigma cik)))) | ||
| 356 | (setq cik (math-add | ||
| 357 | (math-mul sigma cjk) | ||
| 358 | (math-mul gamma cik))) | ||
| 359 | (setq cjk h) | ||
| 360 | (math-nlfit-set-matx-elt C i k cik) | ||
| 361 | (math-nlfit-set-matx-elt C j k cjk) | ||
| 362 | (setq k (1+ k))))) | ||
| 363 | (let* ((di (math-nlfit-get-elt d i)) | ||
| 364 | (dj (math-nlfit-get-elt d j)) | ||
| 365 | (h (math-sub | ||
| 366 | (math-mul gamma dj) | ||
| 367 | (math-mul sigma di)))) | ||
| 368 | (setq di (math-add | ||
| 369 | (math-mul sigma dj) | ||
| 370 | (math-mul gamma di))) | ||
| 371 | (setq dj h) | ||
| 372 | (math-nlfit-set-elt d i di) | ||
| 373 | (math-nlfit-set-elt d j dj)))) | ||
| 374 | (setq i (1+ i)))) | ||
| 375 | (setq j (1+ j))) | ||
| 376 | (let ((i n)) | ||
| 377 | (while (>= i 1) | ||
| 378 | (math-nlfit-set-elt r i 0) | ||
| 379 | (setq s (math-nlfit-get-elt d i)) | ||
| 380 | (let ((k (1+ i))) | ||
| 381 | (while (<= k n) | ||
| 382 | (setq s (math-add s (math-mul (math-nlfit-get-matx-elt C i k) | ||
| 383 | (math-nlfit-get-elt x k)))) | ||
| 384 | (setq k (1+ k)))) | ||
| 385 | (math-nlfit-set-elt x i | ||
| 386 | (math-neg | ||
| 387 | (math-div s | ||
| 388 | (math-nlfit-get-matx-elt C i i)))) | ||
| 389 | (setq i (1- i)))) | ||
| 390 | (let ((i (1+ n))) | ||
| 391 | (while (<= i N) | ||
| 392 | (math-nlfit-set-elt r i (math-nlfit-get-elt d i)) | ||
| 393 | (setq i (1+ i)))) | ||
| 394 | (let ((j n)) | ||
| 395 | (while (>= j 1) | ||
| 396 | (let ((i N)) | ||
| 397 | (while (>= i (1+ j)) | ||
| 398 | (setq rho (math-nlfit-get-matx-elt C i j)) | ||
| 399 | (if (math-equal rho 1) | ||
| 400 | (setq gamma 0 | ||
| 401 | sigma 1) | ||
| 402 | (if (math-lessp (calcFunc-abs rho) 1) | ||
| 403 | (setq sigma rho | ||
| 404 | gamma (calcFunc-sqrt | ||
| 405 | (math-sub 1 (math-mul sigma sigma)))) | ||
| 406 | (setq gamma (math-div 1 (calcFunc-abs rho)) | ||
| 407 | sigma (math-mul (calcFunc-sign rho) | ||
| 408 | (calcFunc-sqrt | ||
| 409 | (math-sub 1 (math-mul gamma gamma))))))) | ||
| 410 | (let ((ri (math-nlfit-get-elt r i)) | ||
| 411 | (rj (math-nlfit-get-elt r j))) | ||
| 412 | (setq h (math-add (math-mul gamma rj) | ||
| 413 | (math-mul sigma ri))) | ||
| 414 | (setq ri (math-sub | ||
| 415 | (math-mul gamma ri) | ||
| 416 | (math-mul sigma rj))) | ||
| 417 | (setq rj h) | ||
| 418 | (math-nlfit-set-elt r i ri) | ||
| 419 | (math-nlfit-set-elt r j rj)) | ||
| 420 | (setq i (1- i)))) | ||
| 421 | (setq j (1- j)))) | ||
| 422 | |||
| 423 | x)) | ||
| 424 | |||
| 425 | (defun math-nlfit-jacobian (grad xlist parms &optional slist) | ||
| 426 | (let ((j nil)) | ||
| 427 | (while xlist | ||
| 428 | (let ((row (apply grad (car xlist) parms))) | ||
| 429 | (setq j | ||
| 430 | (cons | ||
| 431 | (if slist | ||
| 432 | (mapcar (lambda (x) (math-div x (car slist))) row) | ||
| 433 | row) | ||
| 434 | j))) | ||
| 435 | (setq slist (cdr slist)) | ||
| 436 | (setq xlist (cdr xlist))) | ||
| 437 | (reverse j))) | ||
| 438 | |||
| 439 | (defun math-nlfit-make-ident (l n) | ||
| 440 | (let ((m (math-nlfit-make-matrix n n)) | ||
| 441 | (i 1)) | ||
| 442 | (while (<= i n) | ||
| 443 | (math-nlfit-set-matx-elt m i i l) | ||
| 444 | (setq i (1+ i))) | ||
| 445 | m)) | ||
| 446 | |||
| 447 | (defun math-nlfit-chi-sq (xlist ylist parms fn &optional slist) | ||
| 448 | (let ((cs 0)) | ||
| 449 | (while xlist | ||
| 450 | (let ((c | ||
| 451 | (math-sub | ||
| 452 | (apply fn (car xlist) parms) | ||
| 453 | (car ylist)))) | ||
| 454 | (if slist | ||
| 455 | (setq c (math-div c (car slist)))) | ||
| 456 | (setq cs | ||
| 457 | (math-add cs | ||
| 458 | (math-mul c c)))) | ||
| 459 | (setq xlist (cdr xlist)) | ||
| 460 | (setq ylist (cdr ylist)) | ||
| 461 | (setq slist (cdr slist))) | ||
| 462 | cs)) | ||
| 463 | |||
| 464 | (defun math-nlfit-init-lambda (C) | ||
| 465 | (let ((l 0) | ||
| 466 | (n (length (car C))) | ||
| 467 | (N (length C))) | ||
| 468 | (while C | ||
| 469 | (let ((row (car C))) | ||
| 470 | (while row | ||
| 471 | (setq l (math-add l (math-mul (car row) (car row)))) | ||
| 472 | (setq row (cdr row)))) | ||
| 473 | (setq C (cdr C))) | ||
| 474 | (calcFunc-sqrt (math-div l (math-mul n N))))) | ||
| 475 | |||
| 476 | (defun math-nlfit-make-Ctilda (C l) | ||
| 477 | (let* ((n (length (car C))) | ||
| 478 | (bot (math-nlfit-make-ident l n))) | ||
| 479 | (append C bot))) | ||
| 480 | |||
| 481 | (defun math-nlfit-make-d (fn xdata ydata parms &optional sdata) | ||
| 482 | (let ((d nil)) | ||
| 483 | (while xdata | ||
| 484 | (setq d (cons | ||
| 485 | (let ((dd (math-sub (apply fn (car xdata) parms) | ||
| 486 | (car ydata)))) | ||
| 487 | (if sdata (math-div dd (car sdata)) dd)) | ||
| 488 | d)) | ||
| 489 | (setq xdata (cdr xdata)) | ||
| 490 | (setq ydata (cdr ydata)) | ||
| 491 | (setq sdata (cdr sdata))) | ||
| 492 | (reverse d))) | ||
| 493 | |||
| 494 | (defun math-nlfit-make-dtilda (d n) | ||
| 495 | (append d (make-list n 0))) | ||
| 496 | |||
| 497 | (defun math-nlfit-fit (xlist ylist parms fn grad &optional slist) | ||
| 498 | (let* | ||
| 499 | ((C (math-nlfit-jacobian grad xlist parms slist)) | ||
| 500 | (d (math-nlfit-make-d fn xlist ylist parms slist)) | ||
| 501 | (chisq (math-nlfit-chi-sq xlist ylist parms fn slist)) | ||
| 502 | (lambda (math-nlfit-init-lambda C)) | ||
| 503 | (really-done nil) | ||
| 504 | (iters 0)) | ||
| 505 | (while (and | ||
| 506 | (not really-done) | ||
| 507 | (< iters math-nlfit-max-its)) | ||
| 508 | (setq iters (1+ iters)) | ||
| 509 | (let ((done nil)) | ||
| 510 | (while (not done) | ||
| 511 | (let* ((Ctilda (math-nlfit-make-Ctilda C lambda)) | ||
| 512 | (dtilda (math-nlfit-make-dtilda d (length (car C)))) | ||
| 513 | (zeta (math-nlfit-givens Ctilda dtilda)) | ||
| 514 | (newparms (mapcar* 'math-add (copy-tree parms) zeta)) | ||
| 515 | (newchisq (math-nlfit-chi-sq xlist ylist newparms fn slist))) | ||
| 516 | (if (math-lessp newchisq chisq) | ||
| 517 | (progn | ||
| 518 | (if (math-lessp | ||
| 519 | (math-div | ||
| 520 | (math-sub chisq newchisq) newchisq) math-nlfit-epsilon) | ||
| 521 | (setq really-done t)) | ||
| 522 | (setq lambda (math-div lambda 10)) | ||
| 523 | (setq chisq newchisq) | ||
| 524 | (setq parms newparms) | ||
| 525 | (setq done t)) | ||
| 526 | (setq lambda (math-mul lambda 10))))) | ||
| 527 | (setq C (math-nlfit-jacobian grad xlist parms slist)) | ||
| 528 | (setq d (math-nlfit-make-d fn xlist ylist parms slist)))) | ||
| 529 | (list chisq parms))) | ||
| 530 | |||
| 531 | ;;; The functions that describe our models, and their gradients. | ||
| 532 | |||
| 533 | (defun math-nlfit-s-logistic-fn (x a b c) | ||
| 534 | (math-div a (math-add 1 (math-mul b (calcFunc-exp (math-mul c x)))))) | ||
| 535 | |||
| 536 | (defun math-nlfit-s-logistic-grad (x a b c) | ||
| 537 | (let* ((ep (calcFunc-exp (math-mul c x))) | ||
| 538 | (d (math-add 1 (math-mul b ep))) | ||
| 539 | (d2 (math-mul d d))) | ||
| 540 | (list | ||
| 541 | (math-div 1 d) | ||
| 542 | (math-neg (math-div (math-mul a ep) d2)) | ||
| 543 | (math-neg (math-div (math-mul a (math-mul b (math-mul x ep))) d2))))) | ||
| 544 | |||
| 545 | (defun math-nlfit-b-logistic-fn (x a c d) | ||
| 546 | (let ((ex (calcFunc-exp (math-mul c (math-sub x d))))) | ||
| 547 | (math-div | ||
| 548 | (math-mul a ex) | ||
| 549 | (math-sqr | ||
| 550 | (math-add | ||
| 551 | 1 ex))))) | ||
| 552 | |||
| 553 | (defun math-nlfit-b-logistic-grad (x a c d) | ||
| 554 | (let* ((ex (calcFunc-exp (math-mul c (math-sub x d)))) | ||
| 555 | (ex1 (math-add 1 ex)) | ||
| 556 | (xd (math-sub x d))) | ||
| 557 | (list | ||
| 558 | (math-div | ||
| 559 | ex | ||
| 560 | (math-sqr ex1)) | ||
| 561 | (math-sub | ||
| 562 | (math-div | ||
| 563 | (math-mul a (math-mul xd ex)) | ||
| 564 | (math-sqr ex1)) | ||
| 565 | (math-div | ||
| 566 | (math-mul 2 (math-mul a (math-mul xd (math-sqr ex)))) | ||
| 567 | (math-pow ex1 3))) | ||
| 568 | (math-sub | ||
| 569 | (math-div | ||
| 570 | (math-mul 2 (math-mul a (math-mul c (math-sqr ex)))) | ||
| 571 | (math-pow ex1 3)) | ||
| 572 | (math-div | ||
| 573 | (math-mul a (math-mul c ex)) | ||
| 574 | (math-sqr ex1)))))) | ||
| 575 | |||
| 576 | ;;; Functions to get the final covariance matrix and the sdevs | ||
| 577 | |||
| 578 | (defun math-nlfit-find-covar (grad xlist pparms) | ||
| 579 | (let ((j nil)) | ||
| 580 | (while xlist | ||
| 581 | (setq j (cons (cons 'vec (apply grad (car xlist) pparms)) j)) | ||
| 582 | (setq xlist (cdr xlist))) | ||
| 583 | (setq j (cons 'vec (reverse j))) | ||
| 584 | (setq j | ||
| 585 | (math-mul | ||
| 586 | (calcFunc-trn j) j)) | ||
| 587 | (calcFunc-inv j))) | ||
| 588 | |||
| 589 | (defun math-nlfit-get-sigmas (grad xlist pparms chisq) | ||
| 590 | (let* ((sgs nil) | ||
| 591 | (covar (math-nlfit-find-covar grad xlist pparms)) | ||
| 592 | (n (1- (length covar))) | ||
| 593 | (N (length xlist)) | ||
| 594 | (i 1)) | ||
| 595 | (when (> N n) | ||
| 596 | (while (<= i n) | ||
| 597 | (setq sgs (cons (calcFunc-sqrt (nth i (nth i covar))) sgs)) | ||
| 598 | (setq i (1+ i))) | ||
| 599 | (setq sgs (reverse sgs))) | ||
| 600 | (list sgs covar))) | ||
| 601 | |||
| 602 | ;;; Now the Calc functions | ||
| 603 | |||
| 604 | (defun math-nlfit-s-logistic-params (xdata ydata) | ||
| 605 | (let ((pdata (math-nlfit-get-rates-from-cumul xdata ydata))) | ||
| 606 | (math-nlfit-find-logistic-parameters ydata pdata xdata))) | ||
| 607 | |||
| 608 | (defun math-nlfit-b-logistic-params (xdata ydata) | ||
| 609 | (let* ((q0 (math-nlfit-find-q0 ydata xdata)) | ||
| 610 | (qdata (math-nlfit-get-cumul-from-rates xdata ydata q0)) | ||
| 611 | (abc (math-nlfit-find-logistic-parameters qdata ydata xdata)) | ||
| 612 | (B (nth 1 abc)) | ||
| 613 | (C (nth 2 abc)) | ||
| 614 | (A (math-neg | ||
| 615 | (math-mul | ||
| 616 | (nth 0 abc) | ||
| 617 | (math-mul B C)))) | ||
| 618 | (D (math-neg (math-div (calcFunc-ln B) C))) | ||
| 619 | (A (math-div A B))) | ||
| 620 | (list A C D))) | ||
| 621 | |||
| 622 | ;;; Some functions to turn the parameter lists and variables | ||
| 623 | ;;; into the appropriate functions. | ||
| 624 | |||
| 625 | (defun math-nlfit-s-logistic-solnexpr (pms var) | ||
| 626 | (let ((a (nth 0 pms)) | ||
| 627 | (b (nth 1 pms)) | ||
| 628 | (c (nth 2 pms))) | ||
| 629 | (list '/ a | ||
| 630 | (list '+ | ||
| 631 | 1 | ||
| 632 | (list '* | ||
| 633 | b | ||
| 634 | (calcFunc-exp | ||
| 635 | (list '* | ||
| 636 | c | ||
| 637 | var))))))) | ||
| 638 | |||
| 639 | (defun math-nlfit-b-logistic-solnexpr (pms var) | ||
| 640 | (let ((a (nth 0 pms)) | ||
| 641 | (c (nth 1 pms)) | ||
| 642 | (d (nth 2 pms))) | ||
| 643 | (list '/ | ||
| 644 | (list '* | ||
| 645 | a | ||
| 646 | (calcFunc-exp | ||
| 647 | (list '* | ||
| 648 | c | ||
| 649 | (list '- var d)))) | ||
| 650 | (list '^ | ||
| 651 | (list '+ | ||
| 652 | 1 | ||
| 653 | (calcFunc-exp | ||
| 654 | (list '* | ||
| 655 | c | ||
| 656 | (list '- var d)))) | ||
| 657 | 2)))) | ||
| 658 | |||
| 659 | (defun math-nlfit-enter-result (n prefix vals) | ||
| 660 | (setq calc-aborted-prefix prefix) | ||
| 661 | (calc-pop-push-record-list n prefix vals) | ||
| 662 | (calc-handle-whys)) | ||
| 663 | |||
| 664 | (defun math-nlfit-fit-curve (fn grad solnexpr initparms &optional sdv) | ||
| 665 | (calc-slow-wrapper | ||
| 666 | (let* ((sdevv (or (eq sdv 'calcFunc-efit) (eq sdv 'calcFunc-xfit))) | ||
| 667 | (calc-display-working-message nil) | ||
| 668 | (data (calc-top 1)) | ||
| 669 | (xdata (cdr (car (cdr data)))) | ||
| 670 | (ydata (cdr (car (cdr (cdr data))))) | ||
| 671 | (sdata (if (math-contains-sdev-p ydata) | ||
| 672 | (mapcar (lambda (x) (math-get-sdev x t)) ydata) | ||
| 673 | nil)) | ||
| 674 | (ydata (mapcar (lambda (x) (math-get-value x)) ydata)) | ||
| 675 | (zzz (progn (setq j1 xdata j2 ydata j3 sdata) 1)) | ||
| 676 | |||
| 677 | (calc-curve-varnames nil) | ||
| 678 | (calc-curve-coefnames nil) | ||
| 679 | (calc-curve-nvars 1) | ||
| 680 | (fitvars (calc-get-fit-variables 1 3)) | ||
| 681 | (var (nth 1 calc-curve-varnames)) | ||
| 682 | (parms (cdr calc-curve-coefnames)) | ||
| 683 | (parmguess | ||
| 684 | (funcall initparms xdata ydata)) | ||
| 685 | (fit (math-nlfit-fit xdata ydata parmguess fn grad sdata)) | ||
| 686 | (finalparms (nth 1 fit)) | ||
| 687 | (sigmacovar | ||
| 688 | (if sdevv | ||
| 689 | (math-nlfit-get-sigmas grad xdata finalparms (nth 0 fit)))) | ||
| 690 | (sigmas | ||
| 691 | (if sdevv | ||
| 692 | (nth 0 sigmacovar))) | ||
| 693 | (finalparms | ||
| 694 | (if sigmas | ||
| 695 | (mapcar* (lambda (x y) (list 'sdev x y)) finalparms sigmas) | ||
| 696 | finalparms)) | ||
| 697 | (soln (funcall solnexpr finalparms var))) | ||
| 698 | (let ((calc-fit-to-trail t) | ||
| 699 | (traillist nil)) | ||
| 700 | (while parms | ||
| 701 | (setq traillist (cons (list 'calcFunc-eq (car parms) (car finalparms)) | ||
| 702 | traillist)) | ||
| 703 | (setq finalparms (cdr finalparms)) | ||
| 704 | (setq parms (cdr parms))) | ||
| 705 | (setq traillist (calc-normalize (cons 'vec (nreverse traillist)))) | ||
| 706 | (cond ((eq sdv 'calcFunc-efit) | ||
| 707 | (math-nlfit-enter-result 1 "efit" soln)) | ||
| 708 | ((eq sdv 'calcFunc-xfit) | ||
| 709 | (let (sln) | ||
| 710 | (setq sln | ||
| 711 | (list 'vec | ||
| 712 | soln | ||
| 713 | traillist | ||
| 714 | (nth 1 sigmacovar) | ||
| 715 | '(vec) | ||
| 716 | (nth 0 fit) | ||
| 717 | (let ((n (length xdata)) | ||
| 718 | (m (length finalparms))) | ||
| 719 | (if (and sdata (> n m)) | ||
| 720 | (calcFunc-utpc (nth 0 fit) | ||
| 721 | (- n m)) | ||
| 722 | '(var nan var-nan))))) | ||
| 723 | (math-nlfit-enter-result 1 "xfit" sln))) | ||
| 724 | (t | ||
| 725 | (math-nlfit-enter-result 1 "fit" soln))) | ||
| 726 | (calc-record traillist "parm"))))) | ||
| 727 | |||
| 728 | (defun calc-fit-s-shaped-logistic-curve (arg) | ||
| 729 | (interactive "P") | ||
| 730 | (math-nlfit-fit-curve 'math-nlfit-s-logistic-fn | ||
| 731 | 'math-nlfit-s-logistic-grad | ||
| 732 | 'math-nlfit-s-logistic-solnexpr | ||
| 733 | 'math-nlfit-s-logistic-params | ||
| 734 | arg)) | ||
| 735 | |||
| 736 | (defun calc-fit-bell-shaped-logistic-curve (arg) | ||
| 737 | (interactive "P") | ||
| 738 | (math-nlfit-fit-curve 'math-nlfit-b-logistic-fn | ||
| 739 | 'math-nlfit-b-logistic-grad | ||
| 740 | 'math-nlfit-b-logistic-solnexpr | ||
| 741 | 'math-nlfit-b-logistic-params | ||
| 742 | arg)) | ||
| 743 | |||
| 744 | (defun calc-fit-hubbert-linear-curve (&optional sdv) | ||
| 745 | (calc-slow-wrapper | ||
| 746 | (let* ((sdevv (or (eq sdv 'calcFunc-efit) (eq sdv 'calcFunc-xfit))) | ||
| 747 | (calc-display-working-message nil) | ||
| 748 | (data (calc-top 1)) | ||
| 749 | (qdata (cdr (car (cdr data)))) | ||
| 750 | (pdata (cdr (car (cdr (cdr data))))) | ||
| 751 | (sdata (if (math-contains-sdev-p pdata) | ||
| 752 | (mapcar (lambda (x) (math-get-sdev x t)) pdata) | ||
| 753 | nil)) | ||
| 754 | (pdata (mapcar (lambda (x) (math-get-value x)) pdata)) | ||
| 755 | (poverqdata (mapcar* 'math-div pdata qdata)) | ||
| 756 | (parmvals (math-nlfit-least-squares qdata poverqdata sdata sdevv)) | ||
| 757 | (finalparms (list (nth 0 parmvals) | ||
| 758 | (math-neg | ||
| 759 | (math-div (nth 0 parmvals) | ||
| 760 | (nth 1 parmvals))))) | ||
| 761 | (calc-curve-varnames nil) | ||
| 762 | (calc-curve-coefnames nil) | ||
| 763 | (calc-curve-nvars 1) | ||
| 764 | (fitvars (calc-get-fit-variables 1 2)) | ||
| 765 | (var (nth 1 calc-curve-varnames)) | ||
| 766 | (parms (cdr calc-curve-coefnames)) | ||
| 767 | (soln (list '* (nth 0 finalparms) | ||
| 768 | (list '- 1 | ||
| 769 | (list '/ var (nth 1 finalparms)))))) | ||
| 770 | (let ((calc-fit-to-trail t) | ||
| 771 | (traillist nil)) | ||
| 772 | (setq traillist | ||
| 773 | (list 'vec | ||
| 774 | (list 'calcFunc-eq (nth 0 parms) (nth 0 finalparms)) | ||
| 775 | (list 'calcFunc-eq (nth 1 parms) (nth 1 finalparms)))) | ||
| 776 | (cond ((eq sdv 'calcFunc-efit) | ||
| 777 | (math-nlfit-enter-result 1 "efit" soln)) | ||
| 778 | ((eq sdv 'calcFunc-xfit) | ||
| 779 | (let (sln | ||
| 780 | (chisq | ||
| 781 | (math-nlfit-chi-sq | ||
| 782 | qdata poverqdata | ||
| 783 | (list (nth 1 (nth 0 finalparms)) | ||
| 784 | (nth 1 (nth 1 finalparms))) | ||
| 785 | (lambda (x a b) | ||
| 786 | (math-mul a | ||
| 787 | (math-sub | ||
| 788 | 1 | ||
| 789 | (math-div x b)))) | ||
| 790 | sdata))) | ||
| 791 | (setq sln | ||
| 792 | (list 'vec | ||
| 793 | soln | ||
| 794 | traillist | ||
| 795 | (nth 2 parmvals) | ||
| 796 | (list | ||
| 797 | 'vec | ||
| 798 | '(calcFunc-fitdummy 1) | ||
| 799 | (list 'calcFunc-neg | ||
| 800 | (list '/ | ||
| 801 | '(calcFunc-fitdummy 1) | ||
| 802 | '(calcFunc-fitdummy 2)))) | ||
| 803 | chisq | ||
| 804 | (let ((n (length qdata))) | ||
| 805 | (if (and sdata (> n 2)) | ||
| 806 | (calcFunc-utpc | ||
| 807 | chisq | ||
| 808 | (- n 2)) | ||
| 809 | '(var nan var-nan))))) | ||
| 810 | (math-nlfit-enter-result 1 "xfit" sln))) | ||
| 811 | (t | ||
| 812 | (math-nlfit-enter-result 1 "fit" soln))) | ||
| 813 | (calc-record traillist "parm"))))) | ||
| 814 | |||
| 815 | (provide 'calc-nlfit) | ||