comparison lisp/calc/calc-nlfit.el @ 82260:c647b5ba2a46

New file.
author Jay Belanger <jay.p.belanger@gmail.com>
date Sat, 04 Aug 2007 03:54:14 +0000
parents
children f68e9e7acaf6
comparison
equal deleted inserted replaced
82259:4e7d189611f2 82260:c647b5ba2a46
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)