1 ;; A positional derivative package for Maxima.
2 ;; Copyright (C) 2002, 2008, Barton Willis
4 ;; Maxima pdiff is free software; you can redistribute it and/or
5 ;; modify it under the terms of the GNU General Public License,
8 ($put
'$pdiff
1.4 '$version
)
10 ;; Required for wxMaxima display--I switched from ((%pderivop) ...) to (($pderivop) ...).
11 (setf (get '$pderivop
'wxxml
) 'wxxml-pderivop
)
13 ;; When use_pdiff is true, use positional derivatives for unknown
14 ;; non-subscripted functions. By unknown function, I mean a function that
15 ;; is not bound to a formula and that has a derivative that is not known
16 ;; to Maxima. Let's default $use_pdiff to false.
18 (defmvar $use_pdiff nil
)
21 (defmvar $commute_partial_derivatives t
)
23 ;; Return the number of arguments (arity) for f; when the arity is
24 ;; unknown or the arity is variable (for example addition), return false.
25 ;; To attempt to find the arity, we check if f is a user-defined function
26 ;; (examine the $functions list), check if f has a gradef property, check
27 ;; if f is a trig, inverse trig, log or abs function.
29 ;; Subscripted functions aren't members of $functions; for this and
30 ;; other reasons, we disallow subscripted functions from being
31 ;; positionally differentiated.
33 (defun get-number-args (f)
34 (let ((z (cdar (member f
(cdr $functions
) :key
#'caar
))))
35 (cond ((and z
(every #'symbolp z
)) (length z
))
36 ((and (atom f
) (or (trigp f
) (arcp f
) (memq f
'(%log %gamma %erf %sqrt mabs
)))) 1)
37 ((memq f
'($bessel_i $bessel_j $bessel_k $bessel_y
)) 2)
38 ((or (equal f
"^") (equal f
".") (equal f
"^^")) 2)
39 ((and (op-equalp f
'lambda
) (every #'symbolp
(margs (second f
))))
41 ((and (symbolp f
) (get f
'grad
)) (length (first (get f
'grad
))))
42 ((eq t
($constantp f
)) 'constant-function
)
45 ;; pderivop(f,n1,n2,...np) returns the function whose formula is
46 ;; (x1,x2,...,xn) --> diff(f(x1,x2,..xn),x1,n1,x2,n2,...). The
47 ;; pderivop function allows the user to do things like
49 ;; (c1) tellsimpafter(pderivop(f,1)(a),1);
50 ;; (c2) tellsimpafter(pderivop(f,2)(a),1);
51 ;; (c3) diff(f(x),x,2) + diff(f(x),x);
60 ;; (c6) pderivop(g,1);
62 ;; (d7) lambda([i54924], 2 i54924)
67 (defprop $pderivop simp-pderivop operators
)
69 (defun simp-pderivop (x yy z
)
71 (let ((f (simplifya (cadr x
) z
))
72 (n (mapcar #'(lambda (s) (simplifya s z
)) (cddr x
)))
75 (setq nbr-args
(get-number-args f
))
76 (cond ((eq nbr-args
'constant-function
)
77 (cond ((some #'(lambda (s) (and (integerp s
) (> s
0))) n
)
78 `((lambda simp
) ((mlist) ((mlist) $x
)) 0))
80 ((every #'(lambda (s) (and (integerp s
) (= s
0))) n
)
81 `((lambda simp
) ((mlist) ((mlist) $x
)) ,f
))
83 (t `(($pderivop simp
) ,f
,@n
))))
85 ;; When every derivative is 0 (this includes the case that there are no derivatives),
86 ;; return f (even when f is not a function---for example pderivop(a < b) --> a < b).
87 ((every #'(lambda (s) (equal 0 s
)) n
) f
)
91 (if (not (= nbr-args
(length n
)))
92 (merror "~:M expected ~:M derivative argument(s), but it received ~:M" f nbr-args
(length n
))
95 (push (gensym) gen-args
))
96 (push '(mlist) gen-args
)
97 (setq f
($apply f gen-args
))
98 (setq gen-args
(cdr gen-args
))
99 (setq f
(apply '$diff
(cons f
(mapcan #'list gen-args n
))))
100 `((lambda) ((mlist) ,@gen-args
) ,f
))))
102 ;; pderivop(pderivop(m1,m2,...mk),n1,n2,...,nk) --> pderivop(m1+n1,m2+n2,...mk+nk),
103 ;; provided all the m1..nk are explicit nonnegative integers. I could allow declared
104 ;; integers, I suppose. Also, I could give an error when the number of positional
105 ;; derivatives aren't equal--for example pderivop(pderivop(f,1,2),3).
107 ((and (op-equalp f
'$pderivop
)
108 $commute_partial_derivatives
109 (every #'(lambda (s) (and (integerp s
) (> s -
1))) n
)
110 (every #'(lambda (s) (and (integerp s
) (> s -
1))) (cdr (margs f
)))
111 (= (length n
) (- (length (margs f
)) 1)))
112 `(($pderivop simp
) ,(cadr f
) ,@(mapcar #'(lambda (a b
) (add a b
)) (cddr f
) n
)))
114 (t `(($pderivop simp
) ,f
,@n
)))))
116 ;; Extension to mapply1:
118 (setf (get '$pderivop
'mapply1-extension
) 'mapply-pdiff
)
120 (defun mapply-pdiff (fn args fnname form
)
121 (declare (ignore fnname form
))
122 (if (and $use_pdiff
(eq (mop fn
) '$pderivop
))
123 (cond ((equal (length (cddr fn
)) (length args
))
124 `((mqapply simp
) ,fn
,@args
))
126 (merror "The function ~:M expected ~:M argument(s), but it received ~:M"
127 (cadr fn
) (length (cddr fn
)) (length args
))))
131 ;; Extension to sdiffgrad
133 (defun sdiffgrad-pdiff (e x
)
134 (let ((args) (fun (caar e
)) (de) (n) (d-order))
135 (labels ((pderivop (f x n
) (simplify `((mqapply) (($pderivop
) ,f
,@n
) ,@x
)))
137 (let ((k (nth i e
)) (q (copy-list e
)))
138 (setf (nth i q
) (add 1 k
))
140 (i-list (i n
) (incf-ith i
(make-list n
:initial-element
0))))
142 (cond ((and $use_pdiff
(eq fun
'mqapply
) (eq (caaadr e
) '$pderivop
))
144 (setq fun
(cadadr e
))
146 (setq n
(length args
))
147 (setq d-order
(cddadr e
))
149 (setq de
(add de
(mul ($diff
(nth i args
) x
) (pderivop fun args
(incf-ith i d-order
)))))))
151 ;; We disallow positional derivatives of subscripted functions and lambda forms.
153 ((and $use_pdiff
(null (zl-get fun
'grad
)) (not ($subvarp
(cadr e
)))
154 (not (memq fun
'(mlessp mleqp mequal mgeqp mgreaterp mcond lambda
))))
158 (setq n
(length args
))
160 (setq de
(add de
(mul ($diff
(nth i args
) x
) (pderivop fun args
(i-list i n
)))))))
163 ;; Display support for positional derivatives. Indicate the derivative order
164 ;; with a subscript surrounded by parenthesis.
166 (setf (get '$pderivop
'dimension
) 'dimension-pderiv
)
168 (defun dimension-pderiv (form result
)
169 (setq form
(cdr form
))
170 (setq form
`(( ,(car form
) simp array
) (("") ,@(cdr form
))))
171 (dimension-array form result
))
173 ;; Extend tex to handle positional derivatives. Depending on the values of
174 ;; the option variables $tex_uses_prime_for_derivatives
175 ;; and $tex_uses_named_subscripts_for_derivatives, derivatives can
176 ;; tex as superscripted primes, subscripted variable names, or
177 ;; parenthesis surrounded subscripts that indicate the derivative order.
179 (defmvar $tex_uses_prime_for_derivatives nil
)
180 (defmvar $tex_prime_limit
3)
181 (defmvar $tex_uses_named_subscripts_for_derivatives nil
)
182 (defmvar $tex_diff_var_names
(list '(mlist) '$x
'$y
'$z
))
184 (setf (get '$pderivop
'tex
) 'tex-pderivop
)
188 ;; 1. $tex_uses_prime_for_derivatives is true,
190 ;; (c1) tex(diff(f(x),x));
192 ;; (c2) tex(diff(f(x),x,2));
193 ;; $$f^{\prime\prime}(x)$$
194 ;; (c4) tex(diff(f(x),x,4));
197 ;; In the last example, the derivative order exceeds $tex_prime_limit,
198 ;; so the derivative is indicated as shown in (c4).
200 ;; 2. $tex_uses_named subscripts is true
201 ;; (c1) tex_uses_prime_for_derivatives : false;
202 ;; (c2) tex(diff(f(x),x));
204 ;; (c3) tex(diff(f(x),x,2));
206 ;; (c4) tex(diff(f(y),y,2));
209 ;; Although the function argument in (c4) is y, the derivative with
210 ;; respect to the first argument is indicated by a subscript that is
211 ;; the first element of tex_diff_var_names. A further example
213 ;; (c5) tex_diff_var_names : [\a,\b,\c]$
214 ;; (c6) tex(diff(f(x,y,z),x,1,y,1,z,1));
215 ;; $$f_{abc}(x,y,z)$$
217 ;; When the derivative order exceeds tex_prime_limit, we don't use named
218 ;; subscripts for derivatives; otherwise, we could get ridiculously long
221 ;; (c43) tex_prime_limit : 3;
222 ;; (c44) tex(diff(f(x,y),x,1,y,1));
224 ;; (c45) tex(diff(f(x,y),x,1066,y,1776));
225 ;; $$f_{\left(1066,1776\right)}(x,y)$$
227 ;; Finally, setting tex_uses_named subscripts and tex_uses_prime_for_derivatives
228 ;; to false, derivatives are indicated with parenthesis surrounded
229 ;; subscripts. There is one subscript for each function argument; when
230 ;; the derivative order is zero, the subscript is zero.
232 ;; (c11) tex_uses_prime_for_derivatives : false;
233 ;; (c12) tex_uses_named_subscripts_for_derivatives : false;
234 ;; (c13) tex(diff(f(a,b),a,2,b,1));
235 ;; $$f_{\left(2,1\right)}(a,b)$$
236 ;; (c14) tex(diff(f(a,b),a,0,b,1));
237 ;; $$f_{\left(0,1\right)}(a,b)$$
238 ;; (c15) tex(diff(f(x,y),x,0,y,1));
239 ;; $$f_{\left(0,1\right)}(x,y)$$
241 (defun tex-pderivop (x l r
)
242 ;(print `(lop = ,lop rop = ,rop x = ,x r = ,r l = ,l))
243 (cond ((and $tex_uses_prime_for_derivatives
(eql 3 (length x
)))
244 (let* ((n (car (last x
)))
247 (cond ((<= n $tex_prime_limit
)
248 (setq p
(make-list n
:initial-element
"\\prime")))
250 (setq p
(list "(" n
")"))))
252 ;; We need to avoid double tex superscripts; when rop is mexpt, use parens.
254 (cond ((eq rop
'mexpt
)
255 (append l
(list "\\left(") (tex (cadr x
) nil nil lop rop
)
256 (list "^{") p
(list "}") (list "\\right)") r
))
258 (append l
(tex (cadr x
) nil nil lop rop
)
259 (list "^{") p
(list "}") r
)))))
261 ((and $tex_uses_named_subscripts_for_derivatives
262 (< (apply #'+ (cddr x
)) $tex_prime_limit
))
264 (v (mapcar #'stripdollar
(cdr $tex_diff_var_names
)))
267 (cond ((> (length n
) (length v
))
268 (merror "Not enough elements in tex_diff_var_names to tex the expression")))
269 (dotimes (i (length n
))
270 (setq p
(append p
(make-list (nth i n
) :initial-element
(nth i v
)))))
271 (append l
(tex (cadr x
) nil nil lop rop
) (list "_{") p
(list "}") r
)))
274 (append l
(tex (cadr x
) nil nil lop rop
) (list "_{")
275 (tex-matchfix (cons '(mprogn)
276 (cddr x
)) nil nil
) (list "}") r
))))
278 ;; Convert from positional derivatives to non-positional derivatives.
280 (defun $convert_to_diff
(e)
281 (setq e
($totaldisrep e
))
282 (cond (($mapatom e
) e
)
283 ((and (consp e
) (eq (caar e
) 'mqapply
) (eq (caaadr e
) '$pderivop
))
284 (let ((f (second (second e
)))
285 (n (rest (rest (second e
))))
291 ;; Work around bug in Clozure CL: https://github.com/Clozure/ccl/issues/109
292 ;; If ever it's fixed, this conditionalization can be removed.
293 #+ccl
(mapcar #'(lambda (vs) (declare (ignore vs
)) (push (gensym) g
)) v
)
294 #-ccl
(dolist (vs v
) (declare (ignore vs
)) (push (gensym) g
))
295 (setq e
(mapply f g nil
))
296 (setq e
(apply '$diff
(cons e
(mapcan #'list g n
))))
300 (if (symbolp vk
) (setq e
($substitute vk gk e
)) (push (take '(mequal) gk vk
) at-list
)))
301 (if at-list
($at e
(simplify (cons '(mlist) at-list
))) e
)))
302 (t (mapply (mop e
) (mapcar #'$convert_to_diff
(margs e
)) nil
))))