Eliminate spurious redefinition of derivabbrev in Ctensor, fix documentation of diagm...
[maxima/cygwin.git] / src / plot.lisp
blobcfeb04841df1ab6a0f36fc73fbd9982177ff1980
1 ;;Copyright William F. Schelter 1990, All Rights Reserved
3 (in-package :maxima)
5 #|
6 Examples
8 /* plot of z^(1/3)...*/
9 plot3d(r^.33*cos(th/3),[r,0,1],[th,0,6*%pi],['grid,12,80],['transform_xy,polar_to_xy],['plot_format,geomview]) ;
11 /* plot of z^(1/2)...*/
12 plot3d(r^.5*cos(th/2),[r,0,1],[th,0,6*%pi],['grid,12,80],['transform_xy,polar_to_xy],['plot_format,xmaxima]) ;
14 /* moebius */
15 plot3d([cos(x)*(3+y*cos(x/2)),sin(x)*(3+y*cos(x/2)),y*sin(x/2)],[x,-%pi,%pi],[y,-1,1],['grid,50,15]) ;
17 /* klein bottle */
18 plot3d([5*cos(x)*(cos(x/2)*cos(y)+sin(x/2)*sin(2*y)+3.0) - 10.0,
19 -5*sin(x)*(cos(x/2)*cos(y)+sin(x/2)*sin(2*y)+3.0),
20 5*(-sin(x/2)*cos(y)+cos(x/2)*sin(2*y))],[x,-%pi,%pi],[y,-%pi,%pi],
21 ['grid,40,40]) ;
22 /* torus */
23 plot3d([cos(y)*(10.0+6*cos(x)),
24 sin(y)*(10.0+6*cos(x)),
25 -6*sin(x)], [x,0,2*%pi],[y,0,2*%pi],['grid,40,40]) ;
28 (defun ensure-string (x)
29 (cond
30 ((stringp x) x)
31 ((symbolp x) (print-invert-case (stripdollar x)))
32 (t (maybe-invert-string-case (string (implode (strgrind x)))))))
34 (defmfun $join (x y)
35 (if (and ($listp x) ($listp y))
36 (cons '(mlist) (loop for w in (cdr x) for u in (cdr y) collect w collect u))
37 (merror (intl:gettext "join: both arguments must be lists."))))
39 (defun coerce-float (x) ($float (meval* x)))
41 (defvar *maxima-plotdir* "")
42 (declare-top (special *maxima-tempdir* *maxima-prefix*))
44 ;; *ROT* AND FRIENDS ($ROT, $ROTATE_PTS, $ROTATE_LIST) CAN PROBABLY GO AWAY !!
45 ;; THEY ARE UNDOCUMENTED AND UNUSED !!
46 (defvar *rot* (make-array 9 :element-type 'flonum))
47 (defvar $rot nil)
49 ;; Global plot options list. It is not a Maxima variable, to discourage
50 ;; users from changing it directly; it should be changed via set_plot_option
52 (defvar *plot-options*
53 `(:plot_format
54 ,(if (string= *autoconf-win32* "true")
55 '$gnuplot
56 '$gnuplot_pipes)
57 :grid (30 30) :run_viewer t :axes t
58 ;; With adaptive plotting, 29 nticks should be enough; adapt_depth
59 ;; controls the number of splittings adaptive-plotting will do.
60 :nticks 29 :adapt_depth 5
61 :color ($blue $red $green $magenta $black $cyan)
62 :point_type ($bullet $box $triangle $plus $times $asterisk)
63 :palette (((mlist) $gradient $green $cyan $blue $violet)
64 ((mlist) $gradient $magenta $violet $blue $cyan $green $yellow
65 $orange $red $brown $black))
66 :gnuplot_preamble "" :gnuplot_term $default))
68 (defvar $plot_options
69 `((mlist)
70 ((mlist) $plot_format
71 ,(if (string= *autoconf-win32* "true")
72 '$gnuplot
73 '$gnuplot_pipes))))
75 ;; $plot_realpart option is false by default but *plot-realpart* is true
76 ;; because coerce-float-fun is used outside of plot package too.
77 (defvar *plot-realpart* t)
79 (defun maybe-realpart (x)
80 (if *plot-realpart*
81 ($realpart x)
82 (if (zerop1 ($imagpart x))
83 ($realpart x)
84 nil)))
86 (defvar *missing-data-indicator* "NaN")
88 (defvar *gnuplot-stream* nil)
89 (defvar *gnuplot-command* "")
91 (defvar $gnuplot_command (if (string= *autoconf-win32* "true")
92 "wgnuplot"
93 "gnuplot"))
95 (defun start-gnuplot-process (path)
96 #+clisp (setq *gnuplot-stream* (ext:make-pipe-output-stream path))
97 #+lispworks (setq *gnuplot-stream* (system:open-pipe path))
98 #+cmu (setq *gnuplot-stream*
99 (ext:process-input (ext:run-program path nil :input :stream
100 :output nil :wait nil)))
101 #+scl (setq *gnuplot-stream*
102 (ext:process-input (ext:run-program path nil :input :stream
103 :output nil :wait nil)))
104 #+sbcl (setq *gnuplot-stream*
105 (sb-ext:process-input (sb-ext:run-program path nil
106 :input :stream
107 :output nil :wait nil
108 :search t)))
109 #+gcl (setq *gnuplot-stream*
110 (open (concatenate 'string "| " path) :direction :output))
111 #+ecl (progn
112 (setq *gnuplot-stream* (ext:run-program path nil :input :stream :output t :error :output :wait nil)))
113 #+ccl (setf *gnuplot-stream*
114 (ccl:external-process-input-stream
115 (ccl:run-program path nil
116 :wait nil :output nil
117 :input :stream)))
118 #+allegro (setf *gnuplot-stream* (excl:run-shell-command
119 path :input :stream :output nil :wait nil))
120 #+abcl (setq *gnuplot-stream* (system::process-input (system::run-program path nil :wait nil)))
121 #-(or clisp cmu sbcl gcl scl lispworks ecl ccl allegro abcl)
122 (merror (intl:gettext "plotting: I don't know how to tell this Lisp to run Gnuplot."))
124 ;; set mouse must be the first command send to gnuplot
125 (send-gnuplot-command "set mouse"))
127 (defun check-gnuplot-process ()
128 (if (null *gnuplot-stream*)
129 (start-gnuplot-process $gnuplot_command)))
131 (defun $gnuplot_close ()
132 (stop-gnuplot-process)
135 (defun $gnuplot_start ()
136 (check-gnuplot-process)
139 (defun $gnuplot_restart ()
140 ($gnuplot_close)
141 ($gnuplot_start))
143 (defun stop-gnuplot-process ()
144 (unless (null *gnuplot-stream*)
145 (progn
146 (close *gnuplot-stream*)
147 (setq *gnuplot-stream* nil))))
149 (defun send-gnuplot-command (command)
150 (if (null *gnuplot-stream*)
151 (start-gnuplot-process $gnuplot_command))
152 (when (not (null command))
153 (format *gnuplot-stream* "~a ~%" command)
154 (force-output *gnuplot-stream*)))
156 (defun $gnuplot_reset ()
157 (send-gnuplot-command "unset output")
158 (send-gnuplot-command "reset"))
160 (defun $gnuplot_replot (&optional s)
161 (if (null *gnuplot-stream*)
162 (merror (intl:gettext "gnuplot_replot: Gnuplot is not running.")))
163 (cond ((null s)
164 (send-gnuplot-command "replot"))
165 ((stringp s)
166 (send-gnuplot-command s)
167 (send-gnuplot-command "replot"))
169 (merror (intl:gettext "gnuplot_replot: argument, if present, must be a string; found: ~M") s)))
172 ;; allow this to be set in a system init file (sys-init.lsp)
174 (defun $get_plot_option (&optional name n)
175 (let ((options '((mlist))) key value)
176 ;; converts the options property list into a Maxima list
177 (dotimes (i (/ (length *plot-options*) 2))
178 (setq key
179 (intern (concatenate 'string "$"
180 (symbol-name (nth (* i 2) *plot-options*)))))
181 (setq value (nth (+ (* i 2) 1) *plot-options*))
182 (if (consp value)
183 (push (cons '(mlist) (cons key value)) options)
184 (push (list '(mlist) key value) options)))
185 (if name
186 (loop for v in (cdr options)
187 when (eq (second v) name) do (return (if n (nth n v) v)))
188 (reverse options))))
190 (defun quote-strings (opt)
191 (if (atom opt)
192 (if (stringp opt)
193 (format nil "~s" opt)
194 opt)
195 (cons (quote-strings (car opt))
196 (quote-strings (cdr opt)))))
198 (defun get-plot-option-string (option &optional (index 1))
199 (let* ((val ($get_plot_option option 2))
200 (val-list (if ($listp val)
201 (cdr val)
202 `(,val))))
203 (ensure-string (nth (mod (- index 1) (length val-list)) val-list))))
205 (defun $set_plot_option (&rest value)
206 (setq *plot-options* (plot-options-parser value *plot-options*))
207 ($get_plot_option))
209 (defun $remove_plot_option (name)
210 (remf *plot-options*
211 (case name
212 ($adapt_depth :adapt_depth) ($axes :axes) ($azimuth :azimuth)
213 ($box :box) ($color :color) ($color_bar :color_bar)
214 ($color_bar_tics :color_bar_tics) ($elevation :elevation)
215 ($grid :grid) ($grid2d :grid2d) ($iterations :iterations)
216 ($label :label) ($legend :legend) ($logx :logx) ($logy :logy)
217 ($mesh_lines_color :mesh_lines_color) ($nticks :nticks)
218 ($palette :palette) ($plot_format :plot_format)
219 ($plot_realpart :plot_realpart) ($point_type :point_type)
220 ($pdf_file :pdf_file) ($png_file :png_file) ($ps_file :ps_file)
221 ($run_viewer :run_viewer) ($same_xy :samexy)
222 ($same_xyz :same_xyz) ($style :style) ($svg_file :svg_file)
223 ($t :t) ($title :title) ($transform_xy :transform_xy)
224 ($x :x) ($xbounds :xbounds) ($xlabel :xlabel)
225 ($xtics :xtics) ($xvar :xvar) ($xy_scale :xy_scale)
226 ($y :y) ($ybounds :ybounds) ($ylabel :ylabel) ($ytics :ytics)
227 ($yvar :yvar) ($yx_ratio :yx_ratio)
228 ($z :z) ($zlabel :zlabel) ($zmin :zmin) ($ztics :ztics)
229 ($gnuplot_4_0 :gnuplot_4_0)
230 ($gnuplot_curve_titles :gnuplot_curve_titles)
231 ($gnuplot_curve_styles :gnuplot_curve_styles)
232 ($gnuplot_default_term_command :gnuplot_default_term_command)
233 ($gnuplot_dumb_term_command :gnuplot_dumb_term_command)
234 ($gnuplot_out_file :gnuplot_out_file)
235 ($gnuplot_pm3d :gnuplot_pm3d)
236 ($gnuplot_preamble :gnuplot_preamble)
237 ($gnuplot_postamble :gnuplot_postamble)
238 ($gnuplot_pdf_term_command :gnuplot_pdf_term_command)
239 ($gnuplot_png_term_command :gnuplot_png_term_command)
240 ($gnuplot_ps_term_command :gnuplot_ps_term_command)
241 ($gnuplot_svg_term_command :gnuplot_svg_term_command)
242 ($gnuplot_term :gnuplot_term))))
244 (defun get-gnuplot-term (term)
245 (let* ((sterm (string-downcase (ensure-string term)))
246 (pos (search " " sterm)))
247 (if pos
248 (subseq sterm 0 pos)
249 sterm)))
251 (defvar $pstream nil)
253 (defun print-pt1 (f str)
254 (if (floatp f)
255 (format str "~g " f)
256 (format str "~a " *missing-data-indicator*)))
258 (defstruct (polygon (:type list)
259 (:constructor %make-polygon (pts edges)))
260 (dummy '($polygon simp))
261 pts edges)
263 (eval-when
264 #+gcl (compile eval)
265 #-gcl (:compile-toplevel :execute)
267 (defmacro z-pt (ar i) `(aref ,ar (the fixnum (+ 2 (* ,i 3)))))
268 (defmacro y-pt (ar i) `(aref ,ar (the fixnum (1+ (* ,i 3)))))
269 (defmacro x-pt (ar i) `(aref ,ar (the fixnum (* ,i 3))))
270 (defmacro rot (m i j) `(aref ,m (the fixnum (+ ,i (the fixnum (* 3 ,j))))))
272 (defmacro print-pt (f)
273 `(print-pt1 ,f $pstream ))
275 (defmacro make-polygon (a b)
276 `(list '($polygon) ,a ,b)))
278 (defun draw3d (f minx maxx miny maxy nxint nyint)
279 (let* ((epsx (/ (- maxx minx) nxint))
280 (x 0.0) ( y 0.0)
281 (epsy (/ (- maxy miny) nyint))
282 (nx (+ nxint 1))
283 (l 0)
284 (ny (+ nyint 1))
285 (ar (make-array (+ 12 ; 12 for axes
286 (* 3 nx ny)) :fill-pointer (* 3 nx ny)
287 :element-type t :adjustable t)))
288 (declare (type flonum x y epsy epsx)
289 (fixnum nx ny l)
290 (type (cl:array t) ar))
291 (loop for j below ny
292 initially (setq y miny)
293 do (setq x minx)
294 (loop for i below nx
296 (setf (x-pt ar l) x)
297 (setf (y-pt ar l) y)
298 (setf (z-pt ar l) (funcall f x y))
299 (incf l)
300 (setq x (+ x epsx))
302 (setq y (+ y epsy)))
303 (make-polygon ar (make-grid-vertices nxint nyint))))
305 ;; The following is 3x2 = 6 rectangles
306 ;; call (make-vertices 3 2)
307 ;; there are 4x3 = 12 points.
308 ;; ordering is x0,y0,z0,x1,y1,z1,....,x11,y11,z11
309 ;; ----
310 ;; ||||
311 ;; ----
312 ;; ||||
313 ;; ----
315 (defun make-grid-vertices (nx ny)
316 (declare (fixnum nx ny))
317 (let* ((tem (make-array (+ 15 (* 5 nx ny)) :fill-pointer (* 5 nx ny)
318 :adjustable t
319 :element-type '(mod 65000)))
320 (m nx )
321 (nxpt (+ nx 1))
322 (i 0)
324 (declare (fixnum i nxpt m)
325 (type (cl:array (mod 65000)) tem))
326 (loop for k below (length tem)
328 (setf (aref tem k) i)
329 (setf (aref tem (incf k))
330 (+ nxpt i))
331 (setf (aref tem (incf k))
332 (+ nxpt (incf i )))
333 (setf (aref tem (incf k)) i)
334 (setf (aref tem (incf k)) 0) ;place for max
335 (setq m (- m 1))
336 (cond ((eql m 0)
337 (setq m nx)
338 (setq i (+ i 1))))
340 tem))
342 (defun $rotation1 (phi th)
343 (let ((sinph (sin phi))
344 (cosph (cos phi))
345 (sinth (sin th))
346 (costh (cos th)))
347 `(($matrix simp)
348 ((mlist simp) ,(* cosph costh)
349 ,(* -1.0 cosph sinth)
350 ,sinph)
351 ((mlist simp) ,sinth ,costh 0.0)
352 ((mlist simp) ,(- (* sinph costh))
353 ,(* sinph sinth)
354 ,cosph))))
356 ;; pts is a vector of bts [x0,y0,z0,x1,y1,z1,...] and each tuple xi,yi,zi is rotated
357 #-abcl (defun $rotate_pts(pts rotation-matrix)
358 (or ($matrixp rotation-matrix) (merror (intl:gettext "rotate_pts: second argument must be a matrix.")))
359 (let* ((rot *rot*)
360 (l (length pts))
361 (x 0.0) (y 0.0) (z 0.0)
363 (declare (type flonum x y z))
364 (declare (type (cl:array flonum) rot))
365 ($copy_pts rotation-matrix *rot* 0)
367 (loop with j = 0
368 while (< j l)
370 (setq x (aref pts j))
371 (setq y (aref pts (+ j 1)))
372 (setq z (aref pts (+ j 2)))
373 (loop for i below 3 with a of-type flonum = 0.0
375 (setq a (* x (aref rot (+ (* 3 i) 0))))
376 (setq a (+ a (* y (aref rot (+ (* 3 i) 1)))))
377 (setq a (+ a (* z (aref rot (+ (* 3 i) 2)))))
378 (setf (aref pts (+ j i )) a))
379 (setf j (+ j 3)))))
381 (defun $rotate_list (x)
382 (cond ((and ($listp x) (not (mbagp (second x))))
383 ($list_matrix_entries (ncmul2 $rot x)))
384 ((mbagp x) (cons (car x) (mapcar '$rotate_list (cdr x))))))
386 (defun $get_range (pts k &aux (z 0.0) (max most-negative-flonum) (min most-positive-flonum))
387 (declare (type flonum z max min))
388 (declare (type (vector flonum) pts))
389 (loop for i from k below (length pts) by 3
390 do (setq z (aref pts i))
391 (cond ((< z min) (setq min z)))
392 (cond ((> z max) (setq max z))))
393 (list min max (- max min)))
395 (defun $polar_to_xy (pts &aux (r 0.0) (th 0.0))
396 (declare (type flonum r th))
397 (declare (type (cl:array t) pts))
398 (assert (typep pts '(vector t)))
399 (loop for i below (length pts) by 3
400 do (setq r (aref pts i))
401 (setq th (aref pts (+ i 1)))
402 (setf (aref pts i) (* r (cos th)))
403 (setf (aref pts (+ i 1)) (* r (sin th)))))
405 ;; Transformation from spherical coordinates to rectangular coordinates,
406 ;; to be used in plot3d. Example of its use:
407 ;; plot3d (expr, [th, 0, %pi], [ph, 0, 2*%pi], [transform_xy, spherical_to_xyz])
408 ;; where expr gives the value of r in terms of the inclination (th)
409 ;; and azimuth (ph).
411 (defun $spherical_to_xyz (pts &aux (r 0.0) (th 0.0) (ph 0.0))
412 (declare (type flonum r th ph))
413 (declare (type (cl:array t) pts))
414 (assert (typep pts '(vector t)))
415 (loop for i below (length pts) by 3
416 do (setq th (aref pts i))
417 (setq ph (aref pts (+ i 1)))
418 (setq r (aref pts (+ i 2)))
419 (setf (aref pts i) (* r (sin th) (cos ph)))
420 (setf (aref pts (+ i 1)) (* r (sin th) (sin ph)))
421 (setf (aref pts (+ i 2)) (* r (cos th)))))
424 ;; return a function suitable for the transform function in plot3d.
425 ;; FX, FY, and FZ are functions of three arguments.
426 (defun $make_transform (lvars fx fy fz)
427 (setq fx (coerce-float-fun fx lvars))
428 (setq fy (coerce-float-fun fy lvars))
429 (setq fz (coerce-float-fun fz lvars))
430 (let ((sym (gensym "transform")))
431 (setf (symbol-function sym)
432 #'(lambda (pts &aux (x1 0.0)(x2 0.0)(x3 0.0))
433 (declare (type flonum x1 x2 x3))
434 (declare (type (cl:array t) pts))
435 (loop for i below (length pts) by 3
437 (setq x1 (aref pts i))
438 (setq x2 (aref pts (+ i 1)))
439 (setq x3 (aref pts (+ i 2)))
440 (setf (aref pts i) (funcall fx x1 x2 x3))
441 (setf (aref pts (+ 1 i)) (funcall fy x1 x2 x3))
442 (setf (aref pts (+ 2 i)) (funcall fz x1 x2 x3)))))))
444 ;; Return value is a Lisp function which evaluates EXPR to a float.
445 ;; COERCE-FLOAT-FUN always returns a function and never returns a symbol,
446 ;; even if EXPR is a symbol.
448 ;; Following cases are recognized:
449 ;; EXPR is a symbol
450 ;; name of a Lisp function
451 ;; name of a Maxima function
452 ;; name of a DEFMSPEC function
453 ;; name of a Maxima macro
454 ;; a string which is the name of a Maxima operator (e.g., "!")
455 ;; name of a simplifying function
456 ;; EXPR is a Maxima lambda expression
457 ;; EXPR is a general Maxima expression
459 ;; %COERCE-FLOAT-FUN is the main internal routine for this.
460 ;; COERCE-FLOAT-FUN is the user interface for creating a function that
461 ;; returns floats. COERCE-BFLOAT-FUN is the same, except bfloats are
462 ;; returned.
463 (defun %coerce-float-fun (float-fun expr &optional lvars)
464 (cond ((and (consp expr) (functionp expr))
465 (let ((args (if lvars (cdr lvars) (list (gensym)))))
466 (coerce-lisp-function-or-lisp-lambda args expr :float-fun float-fun)))
467 ;; expr is a string which names an operator
468 ;; (e.g. "!" "+" or a user-defined operator)
469 ((and (stringp expr) (getopr0 expr))
470 (let ((a (if lvars lvars `((mlist) ,(gensym)))))
471 (%coerce-float-fun float-fun `(($apply) ,(getopr0 expr) ,a) a)))
472 ((and (symbolp expr) (not (member expr lvars)) (not ($constantp expr)))
473 (cond
474 ((fboundp expr)
475 (let ((args (if lvars (cdr lvars) (list (gensym)))))
476 (coerce-lisp-function-or-lisp-lambda args expr :float-fun float-fun)))
478 ;; expr is name of a Maxima function defined by := or
479 ;; define
480 ((mget expr 'mexpr)
481 (let*
482 ((mexpr (mget expr 'mexpr))
483 (args (cdr (second mexpr))))
484 (coerce-maxima-function-or-maxima-lambda args expr :float-fun float-fun)))
486 ((or
487 ;; expr is the name of a function defined by defmspec
488 (get expr 'mfexpr*)
489 ;; expr is the name of a Maxima macro defined by ::=
490 (mget expr 'mmacro)
491 ;; expr is the name of a simplifying function, and the
492 ;; simplification property is associated with the noun
493 ;; form
494 (get ($nounify expr) 'operators)
495 ;; expr is the name of a simplifying function, and the
496 ;; simplification property is associated with the verb
497 ;; form
498 (get ($verbify expr) 'operators))
499 (let ((a (if lvars lvars `((mlist) ,(gensym)))))
500 (%coerce-float-fun float-fun `(($apply) ,expr ,a) a)))
502 (merror (intl:gettext "COERCE-FLOAT-FUN: no such Lisp or Maxima function: ~M") expr))))
504 ((and (consp expr) (eq (caar expr) 'lambda))
505 (let ((args (cdr (second expr))))
506 (coerce-maxima-function-or-maxima-lambda args expr :float-fun float-fun)))
509 (let* ((vars (or lvars ($sort ($listofvars expr))))
510 (subscripted-vars ($sublist vars '((lambda) ((mlist) $x) ((mnot) (($atom) $x)))))
511 gensym-vars save-list-gensym subscripted-vars-save
512 subscripted-vars-mset subscripted-vars-restore)
514 ;; VARS and SUBSCRIPTED-VARS are Maxima lists. Other lists are
515 ;; Lisp lists.
516 (when (cdr subscripted-vars)
517 (setq gensym-vars (mapcar #'(lambda (ign) (declare (ignore ign)) (gensym))
518 (cdr subscripted-vars)))
519 (mapcar #'(lambda (a b) (setq vars (subst b a vars :test 'equal)))
520 (cdr subscripted-vars) gensym-vars)
522 ;; This stuff about saving and restoring array variables
523 ;; should go into MBINDING, and the lambda expression
524 ;; constructed below should call MBINDING. (At present
525 ;; MBINDING barfs on array variables.)
526 (setq save-list-gensym (gensym))
527 (setq subscripted-vars-save
528 (mapcar #'(lambda (a) `(push (meval ',a) ,save-list-gensym))
529 (cdr subscripted-vars)))
530 (setq subscripted-vars-mset
531 (mapcar #'(lambda (a b) `(mset ',a ,b))
532 (cdr subscripted-vars) gensym-vars))
533 (setq subscripted-vars-restore
534 (mapcar #'(lambda (a) `(mset ',a (pop ,save-list-gensym)))
535 (reverse (cdr subscripted-vars)))))
537 (coerce
538 `(lambda ,(cdr vars)
539 (declare (special ,@(cdr vars) errorsw))
541 ;; Nothing interpolated here when there are no subscripted
542 ;; variables.
543 ,@(if save-list-gensym `((declare (special ,save-list-gensym))))
545 ;; Nothing interpolated here when there are no subscripted
546 ;; variables.
547 ,@(if (cdr subscripted-vars)
548 `((progn (setq ,save-list-gensym nil)
549 ,@(append subscripted-vars-save subscripted-vars-mset))))
551 (let (($ratprint nil)
552 ;; We don't want to set $numer to T when coercing
553 ;; to a bigfloat. By doing so, things like
554 ;; log(400)^400 get converted to double-floats,
555 ;; which causes a double-float overflow. But the
556 ;; whole point of coercing to bfloat is to use
557 ;; bfloats, not doubles.
559 ;; Perhaps we don't even need to do this for
560 ;; double-floats? It would be nice to remove
561 ;; this. For backward compatibility, we bind
562 ;; numer to T if we're not trying to bfloat.
563 ($numer ,(not (eq float-fun '$bfloat)))
564 (*nounsflag* t)
565 (errorsw t)
566 (errcatch t))
567 (declare (special errcatch))
568 ;; Catch any errors from evaluating the
569 ;; function. We're assuming that if an error
570 ;; is caught, the result is not a number. We
571 ;; also assume that for such errors, it's
572 ;; because the function is not defined there,
573 ;; not because of some other maxima error.
575 ;; GCL 2.6.2 has handler-case but not quite ANSI yet.
576 (let ((result
577 #-gcl
578 (handler-case
579 (catch 'errorsw
580 (,float-fun (maybe-realpart (meval* ',expr))))
581 ;; Should we just catch all errors here? It is
582 ;; rather nice to only catch errors we care
583 ;; about and let other errors fall through so
584 ;; that we don't pretend to do something when
585 ;; it is better to let the error through.
586 (arithmetic-error () t)
587 (maxima-$error () t))
588 #+gcl
589 (handler-case
590 (catch 'errorsw
591 (,float-fun (maybe-realpart (meval* ',expr))))
592 (cl::error () t))
595 ;; Nothing interpolated here when there are no
596 ;; subscripted variables.
597 ,@(if (cdr subscripted-vars) `((progn ,@subscripted-vars-restore)))
599 result)))
600 'function)))))
602 (defun coerce-float-fun (expr &optional lvars)
603 (%coerce-float-fun '$float expr lvars))
605 (defun coerce-bfloat-fun (expr &optional lvars)
606 (%coerce-float-fun '$bfloat expr lvars))
608 (defun coerce-maxima-function-or-maxima-lambda (args expr &key (float-fun '$float))
609 (let ((gensym-args (loop for x in args collect (gensym))))
610 (coerce
611 `(lambda ,gensym-args (declare (special ,@gensym-args))
612 (let* (($ratprint nil)
613 ($numer t)
614 (*nounsflag* t)
615 (errorsw t)
616 (errcatch t))
617 (declare (special errcatch))
618 ;; Just always try to convert the result to a float,
619 ;; which handles things like $%pi. See also BUG
620 ;; #2880115
621 ;; http://sourceforge.net/tracker/?func=detail&atid=104933&aid=2880115&group_id=4933
623 ;; Should we use HANDLER-CASE like we do above in
624 ;; %coerce-float-fun? Seems not necessary for what we want
625 ;; to do.
626 (catch 'errorsw
627 (,float-fun
628 (maybe-realpart (mapply ',expr (list ,@gensym-args) t))))))
629 'function)))
631 ;; Same as above, but call APPLY instead of MAPPLY.
633 (defun coerce-lisp-function-or-lisp-lambda (args expr &key (float-fun '$float))
634 (let ((gensym-args (loop for x in args collect (gensym))))
635 (coerce
636 `(lambda ,gensym-args (declare (special ,@gensym-args))
637 (let* (($ratprint nil)
638 ($numer t)
639 (*nounsflag* t)
640 (result (maybe-realpart (apply ',expr (list ,@gensym-args)))))
641 ;; Always use $float. See comment for
642 ;; coerce-maxima-function-ormaxima-lambda above.
643 (,float-fun result)))
644 'function)))
646 (defmacro zval (points verts i) `(aref ,points (+ 2 (* 3 (aref ,verts ,i)))))
648 ;;sort the edges array so that drawing the edges will happen from the back towards
649 ;; the front. The if n==4 the edges array coming in looks like
650 ;; v1 v2 v3 v4 0 w1 w2 w3 w4 0 ...
651 ;; where vi,wi are indices pointint into the points array specifiying a point
652 ;; in 3 space. After the sorting is done, the 0 is filled in with the vertex
653 ;; which is closer to us (ie highest z component after rotating towards the user)
654 ;; and this is then they are sorted in groups of 5.
655 (defun sort-ngons (points edges n &aux lis )
656 (declare (type (cl:array (flonum)) points)
657 (type (cl:array (mod 65000)) edges)
658 (fixnum n))
659 (let ((new (make-array (length edges) :element-type (array-element-type edges)))
660 (i 0)
661 (z 0.0)
662 (z1 0.0)
663 (n1 (- n 1))
664 (at 0)
665 (leng (length edges))
667 (declare (type (cl:array (mod 65000)) new)
668 (fixnum i leng n1 at )
670 (declare (type flonum z z1))
672 (setq lis
673 (loop for i0 below leng by (+ n 1)
675 (setq i i0)
676 (setq at 0)
677 (setq z (zval points edges i))
678 (setq i (+ i 1))
679 (loop for j below n1
680 do (if (> (setq z1 (zval points edges i)) z)
681 (setq z z1 at (aref edges i) ))
682 (setq i (+ i 1))
684 (setf (aref edges i) at)
685 collect (cons z i0)))
686 (setq lis (sort lis #'alphalessp :key #'car))
687 (setq i 0)
688 (loop for v in lis
690 (loop for j from (cdr v)
691 for k to n
692 do (setf (aref new i) (aref edges j))
693 (incf i))
695 (copy-array-portion edges new 0 0 (length edges))
698 (defun copy-array-portion (ar1 ar2 i1 i2 n1)
699 (declare (fixnum i1 i2 n1))
700 (loop while (>= (setq n1 (- n1 1)) 0)
701 do (setf (aref ar1 i1) (aref ar2 i2))
702 (setq i1 (+ i1 1))
703 (setq i2 (+ i2 1))))
706 (defun $concat_polygons (pl1 pl2 &aux tem new)
707 (setq new
708 (loop for v in pl1
709 for w in pl2
710 for l = (+ (length v) (length w))
711 do (setq tem (make-array l
712 :element-type (array-element-type v)
713 :fill-pointer l
716 collect tem))
717 (setq new (make-polygon (first new) (second new)) )
719 (copy-array-portion (polygon-pts pl1) (polygon-pts new)
720 0 0 (length (polygon-pts pl1)))
721 (copy-array-portion (polygon-pts pl2) (polygon-pts new)
722 (length (polygon-pts pl1))
723 0 (length (polygon-pts pl2)))
724 (copy-array-portion (polygon-edges pl1) (polygon-edges new)
725 0 0 (length (polygon-edges pl1)))
726 (loop for i from (length (polygon-edges pl1))
727 for j from 0 below (length (polygon-edges pl2))
728 with lpts1 = (length (polygon-pts pl1))
729 with ar2 = (polygon-edges pl2)
730 with arnew = (polygon-edges new)
731 do (setf (aref arnew i) (+ lpts1 (aref ar2 j)))))
733 (defun $copy_pts(lis vec start)
734 (declare (fixnum start))
735 (let ((tem vec))
736 (declare (type (cl:array flonum) tem))
737 (cond ((numberp lis)
738 (or (typep lis 'flonum) (setq lis (float lis)))
739 (setf (aref tem start) lis)
740 (1+ start))
741 ((typep lis 'cons)
742 ($copy_pts (cdr lis) vec ($copy_pts (car lis) vec start)))
743 ((symbolp lis) start)
744 (t (merror (intl:gettext "copy_pts: unrecognized first argument: ~M") lis)))))
746 ;; parametric ; [parametric,xfun,yfun,[t,tlow,thigh],[nticks ..]]
747 ;; the rest of the parametric list after the list will add to the plot options
749 ;; TODO: This should be removed after some time, once adaptive
750 ;; plotting has received enough testing and debugging.
751 (defmvar $use_adaptive_parametric_plot t
752 "If true, parametric plots use adaptive plotting")
754 (defun draw2d-parametric (param range1 plot-options &aux range tem)
755 (cond ((and ($listp (setq tem (nth 4 param)))
756 (symbolp (cadr tem))
757 (eql ($length tem) 3))
758 ;; sure looks like a range
759 (setq range (check-range tem))))
760 (let* ((options
761 (plot-options-parser
762 (if range1 (cons range1 (cddddr param)) (cddddr param))
763 plot-options))
764 (nticks (getf options :nticks))
765 (trange (or (cddr range) (getf options :t)))
766 (tvar (or (cadr range) '$t))
767 (xrange (or (getf options :x) (getf options :xbounds)))
768 (yrange (or (getf options :y) (getf options :ybounds)))
769 (tmin (coerce-float (first trange)))
770 (tmax (coerce-float (second trange)))
771 (xmin (coerce-float (first xrange)))
772 (xmax (coerce-float (second xrange)))
773 (ymin (coerce-float (first yrange)))
774 (ymax (coerce-float (second yrange)))
775 (x 0.0) ; have to initialize to some floating point..
776 (y 0.0)
777 (tt tmin)
778 (eps (/ (- tmax tmin) (- nticks 1)))
779 f1 f2 in-range-y in-range-x in-range last-ok
781 (declare (type flonum x y tt ymin ymax xmin xmax tmin tmax eps))
782 (setq f1 (coerce-float-fun (third param) `((mlist), tvar)))
783 (setq f2 (coerce-float-fun (fourth param) `((mlist), tvar)))
784 (cons '(mlist simp)
785 (loop
787 (setq x (funcall f1 tt))
788 (setq y (funcall f2 tt))
789 (setq in-range-y (and (<= y ymax) (>= y ymin)))
790 (setq in-range-x (and (<= x xmax) (>= x xmin)))
791 (setq in-range (and in-range-x in-range-y))
792 when (and (not in-range) (not last-ok))
793 collect 'moveto and collect 'moveto
795 (setq last-ok in-range)
796 collect (if in-range-x x (if (> x xmax) xmax xmin))
797 collect (if in-range-y y (if (> y ymax) ymax ymin))
798 when (>= tt tmax) do (loop-finish)
799 do (setq tt (+ tt eps))
800 (if (>= tt tmax) (setq tt tmax))
804 (defun draw2d-parametric-adaptive (param range1 plot-options &aux range tem)
805 (cond ((and ($listp (setq tem (nth 4 param)))
806 (symbolp (cadr tem))
807 (eql ($length tem) 3))
808 ;; sure looks like a range
809 (setq range (check-range tem))))
810 (let* ((options
811 (plot-options-parser
812 (if range1 (cons range1 (cddddr param)) (cddddr param))
813 plot-options))
814 (nticks (getf options :nticks))
815 (trange (or (cddr range) (getf options :t)))
816 (tvar (or (cadr range) '$t))
817 (xrange (or (getf options :x) (getf options :xbounds)))
818 (yrange (or (getf options :y) (getf options :ybounds)))
819 (tmin (coerce-float (first trange)))
820 (tmax (coerce-float (second trange)))
821 (xmin (coerce-float (first xrange)))
822 (xmax (coerce-float (second xrange)))
823 (ymin (coerce-float (first yrange)))
824 (ymax (coerce-float (second yrange)))
825 f1 f2)
826 (declare (type flonum ymin ymax xmin xmax tmin tmax))
827 (setq f1 (coerce-float-fun (third param) `((mlist), tvar)))
828 (setq f2 (coerce-float-fun (fourth param) `((mlist), tvar)))
830 (let ((n-clipped 0) (n-non-numeric 0)
831 (t-step (/ (- tmax tmin) (coerce-float nticks) 2))
832 t-samples x-samples y-samples result)
833 ;; Divide the range into 2*NTICKS regions that we then
834 ;; adaptively plot over.
835 (dotimes (k (1+ (* 2 nticks)))
836 (let ((tpar (+ tmin (* k t-step))))
837 (push tpar t-samples)
838 (push (funcall f1 tpar) x-samples)
839 (push (funcall f2 tpar) y-samples)))
840 (setf t-samples (nreverse t-samples))
841 (setf x-samples (nreverse x-samples))
842 (setf y-samples (nreverse y-samples))
844 ;; Adaptively plot over each region
845 (do ((t-start t-samples (cddr t-start))
846 (t-mid (cdr t-samples) (cddr t-mid))
847 (t-end (cddr t-samples) (cddr t-end))
848 (x-start x-samples (cddr x-start))
849 (x-mid (cdr x-samples) (cddr x-mid))
850 (x-end (cddr x-samples) (cddr x-end))
851 (y-start y-samples (cddr y-start))
852 (y-mid (cdr y-samples) (cddr y-mid))
853 (y-end (cddr y-samples) (cddr y-end)))
854 ((null t-end))
855 (setf result
856 (if result
857 (append result
858 (cddr (adaptive-parametric-plot
859 f1 f2
860 (car t-start) (car t-mid) (car t-end)
861 (car x-start) (car x-mid) (car x-end)
862 (car y-start) (car y-mid) (car y-end)
863 (getf options :adapt_depth)
864 1e-5)))
865 (adaptive-parametric-plot
866 f1 f2
867 (car t-start) (car t-mid) (car t-end)
868 (car x-start) (car x-mid) (car x-end)
869 (car y-start) (car y-mid) (car y-end)
870 (getf options :adapt_depth)
871 1e-5))))
872 ;; Fix up out-of-range values and clobber non-numeric values.
873 (do ((x result (cddr x))
874 (y (cdr result) (cddr y)))
875 ((null y))
876 (if (and (numberp (car x)) (numberp (car y)))
877 (unless (and (<= ymin (car y) ymax)
878 (<= xmin (car x) xmax))
879 (incf n-clipped)
880 (setf (car x) 'moveto)
881 (setf (car y) 'moveto))
882 (progn
883 (incf n-non-numeric)
884 (setf (car x) 'moveto)
885 (setf (car y) 'moveto))))
886 ;; Filter out any MOVETO's which do not precede a number.
887 ;; Code elsewhere in this file expects MOVETO's to
888 ;; come in pairs, so leave two MOVETO's before a number.
889 (let ((n (length result)))
890 (dotimes (i n)
891 (when
892 (and
893 (evenp i)
894 (eq (nth i result) 'moveto)
895 (eq (nth (1+ i) result) 'moveto)
896 (or
897 (eq i (- n 2))
898 (eq (nth (+ i 2) result) 'moveto)))
899 (setf (nth i result) nil)
900 (setf (nth (1+ i) result) nil))))
902 (let ((result-sans-nil (delete nil result)))
903 (if (null result-sans-nil)
904 (cond
905 ((= n-non-numeric 0)
906 (mtell (intl:gettext "plot2d: all values were clipped.~%")))
907 ((= n-clipped 0)
908 (mtell (intl:gettext
909 "plot2d: expression evaluates to non-numeric value everywhere in plotting range.~%")))
911 (mtell (intl:gettext
912 "plot2d: all values are non-numeric, or clipped.~%"))))
913 (progn
914 (if (> n-non-numeric 0)
915 (mtell (intl:gettext
916 "plot2d: expression evaluates to non-numeric value somewhere in plotting range.~%")))
917 (if (> n-clipped 0)
918 (mtell (intl:gettext "plot2d: some values were clipped.~%")))))
919 (cons '(mlist) result-sans-nil)))))
921 ;; draw2d-discrete. Accepts [discrete,[x1,x2,...],[y1,y2,...]]
922 ;; or [discrete,[[x1,y1]...] and returns [x1,y1,...] or nil, if
923 ;; non of the points have real values.
924 ;; Currently any options given are being ignored, because there
925 ;; are no options specific to the generation of the points.
926 (defun draw2d-discrete (f)
927 (let ((x (third f)) (y (fourth f)) data gaps)
928 (cond
929 (($listp x) ; x is a list
930 (cond
931 (($listp (cadr x)) ; x1 is a list
932 (cond
933 ((= (length (cadr x)) 3) ; x1 is a 2D point
934 (setq data (parse-points-xy x)))
935 (t ; x1 is not a 2D point
936 (merror (intl:gettext "draw2d-discrete: Expecting a point with 2 coordinates; found ~M~%") (cadr x)))))
937 (t ; x1 is not a list
938 (cond
939 (($listp y) ; y is a list
940 (cond
941 ((symbolp (coerce-float (cadr y))); y is an option
942 (setq data (parse-points-y x)))
943 (t ; y is not an option
944 (cond
945 (($listp (cadr y)) ; y1 is a list
946 (merror (intl:gettext "draw2d-discrete: Expecting a y coordinate; found ~M~%") (cadr y)))
947 (t ; y1 not a list
948 (cond
949 ((= (length x) (length y)) ; case [x][y]
950 (setq data (parse-points-x-y x y)))
951 (t ; wrong
952 (merror (intl:gettext "draw2d-discrete: The number of x and y coordinates do not match.~%")))))))))
953 (t ; y is not a list
954 (setq data (parse-points-y x)))))))
955 (t ; x is not a list
956 (merror (intl:gettext "draw2d-discrete: Expecting a list of x coordinates or points; found ~M~%") x)))
958 ;; checks for non-real values
959 (cond
960 ((some #'realp data)
961 (setq gaps (count-if #'(lambda (x) (eq x 'moveto)) data))
962 (when (> gaps 0)
963 ;; some points have non-real values
964 (mtell (intl:gettext "Warning: excluding ~M points with non-numerical values.~%") (/ gaps 2))))
966 ;; none of the points have real values
967 (mtell (intl:gettext "Warning: none of the points have numerical values.~%"))
968 (setq data nil)))
969 data))
971 ;; Two lists [x1...xn] and [y1...yn] are joined as
972 ;; [x1 y1...xn yn], converting all expressions to real numbers.
973 ;; If either xi or yi are not real, both are replaced by 'moveto
974 (defun parse-points-x-y (x y)
975 (do ((a (rest x) (cdr a))
976 (b (rest y) (cdr b))
977 c af bf)
978 ((null b) (cons '(mlist) (reverse c)))
979 (setq af (coerce-float (car a)))
980 (setq bf (coerce-float (car b)))
981 (cond
982 ((or (not (realp af)) (not (realp bf)))
983 (setq c (cons 'moveto (cons 'moveto c))))
985 (setq c (cons bf (cons af c)))))))
987 ;; One list [y1...yn] becomes the list [1 y1...n yn],
988 ;; converting all expressions to real numbers.
989 ;; If yi is not real, both i and yi are replaced by 'moveto
990 (defun parse-points-y (y)
991 (do ((a 1 (1+ a))
992 (b (rest y) (cdr b))
993 c bf)
994 ((null b) (cons '(mlist) (reverse c)))
995 (setq bf (coerce-float (car b)))
996 (cond
997 ((not (realp bf))
998 (setq c (cons 'moveto (cons 'moveto c))))
1000 (setq c (cons bf (cons a c)))))))
1002 ;; List [[x1,y1]...[xn,yn]] is transformed into
1003 ;; [x1 y1...xn yn], converting all expressions to real numbers.
1004 ;; If either xi or yi are not real, both are replaced by 'moveto
1005 (defun parse-points-xy (xy)
1006 (do ((ab (rest xy) (cdr ab))
1007 c af bf)
1008 ((null ab) (cons '(mlist) (reverse c)))
1009 (setq af (coerce-float (cadar ab)))
1010 (setq bf (coerce-float (caddar ab)))
1011 (cond
1012 ((or (not (realp af)) (not (realp bf)))
1013 (setq c (cons 'moveto (cons 'moveto c))))
1015 (setq c (cons bf (cons af c)))))))
1017 ;;; Adaptive plotting, based on the adaptive plotting code from
1018 ;;; YACAS. See http://yacas.sourceforge.net/Algo.html#c3s1 for a
1019 ;;; description of the algorithm. More precise details can be found
1020 ;;; in the file yacas/scripts/plots.rep/plot2d.ys.
1023 ;; Determine if we have a slow oscillation of the function.
1024 ;; Basically, for each 3 consecutive function values, we check to see
1025 ;; if the function is monotonic or not. There are 3 such sets, and
1026 ;; the function is considered slowly oscillating if at most 2 of them
1027 ;; are not monotonic.
1028 (defun slow-oscillation-p (f0 f1 f2 f3 f4)
1029 (flet ((sign-change (x y z)
1030 (cond ((not (and (numberp x) (numberp y) (numberp z)))
1031 ;; Something is not a number. Assume the
1032 ;; oscillation is not slow.
1034 ((or (and (> y x) (> y z))
1035 (and (< y x) (< y z)))
1038 0))))
1039 (<= (+ (sign-change f0 f1 f2)
1040 (sign-change f1 f2 f3)
1041 (sign-change f2 f3 f4))
1042 2)))
1044 ;; Determine if the function values are smooth enough. This means
1045 ;; that integrals of the functions on the left part and the right part
1046 ;; of the range are approximately the same.
1049 (defun smooth-enough-p (f-a f-a1 f-b f-b1 f-c eps)
1050 (cond ((every #'numberp (list f-a f-a1 f-b f-b1 f-c))
1051 (let ((quad (/ (+ f-a
1052 (* -5 f-a1)
1053 (* 9 f-b)
1054 (* -7 f-b1)
1055 (* 2 f-c))
1056 24))
1057 (quad-b (/ (+ (* 5 f-b)
1058 (* 8 f-b1)
1059 (- f-c))
1060 12)))
1061 ;; According to the Yacas source code, quad is the Simpson
1062 ;; quadrature for the (fb,fb1) subinterval (using points b,b1,c),
1063 ;; subtracted from the 4-point Newton-Cotes quadrature for the
1064 ;; (fb,fb1) subinterval (using points a, a1, b, b1.
1066 ;; quad-b is the Simpson quadrature for the (fb,f1) subinterval.
1068 ;; This used to test for diff <= 0. But in some
1069 ;; situations, like plot2d(0.99,[x,0,5]), roundoff prevents
1070 ;; this from happening. So we do diff < delta instead, for
1071 ;; some value of delta.
1073 ;; XXX: What is the right value for delta? Does this break
1074 ;; other things? Simple tests thus far show that
1075 ;; 100*flonum-epsilon is ok.
1076 (let ((diff (- (abs quad)
1077 (* eps (- quad-b (min f-a f-a1 f-b f-b1 f-c)))))
1078 (delta (* 150 flonum-epsilon)))
1079 (<= diff delta))))
1081 ;; Something is not a number, so assume it's not smooth enough.
1082 nil)))
1085 (defun adaptive-plot (fcn a b c f-a f-b f-c depth eps)
1086 ;; Step 1: Split the interval [a, c] into 5 points
1087 (let* ((a1 (/ (+ a b) 2))
1088 (b1 (/ (+ b c) 2))
1089 (f-a1 (funcall fcn a1))
1090 (f-b1 (funcall fcn b1))
1092 (cond ((or (not (plusp depth))
1093 (and (slow-oscillation-p f-a f-a1 f-b f-b1 f-c)
1094 (smooth-enough-p f-a f-a1 f-b f-b1 f-c eps)))
1095 ;; Everything is nice and smooth so we're done. Don't
1096 ;; refine anymore.
1097 (list a f-a
1098 a1 f-a1
1099 b f-b
1100 b1 f-b1
1101 c f-c))
1102 ;; We are not plotting the real part of the function and the
1103 ;; function is undefined at all points - assume it has complex value
1104 ;; on [a,b]. Maybe we should refine it a couple of times just to make sure?
1105 ((and (null *plot-realpart*)
1106 (null f-a) (null f-a1) (null f-b) (null f-b1) (null f-c))
1107 (list a f-a
1108 a1 f-a1
1109 b f-b
1110 b1 f-b1
1111 c f-c))
1113 ;; Need to refine. Split the interval in half, and try to plot each half.
1114 (let ((left (adaptive-plot fcn a a1 b f-a f-a1 f-b (1- depth) (* 2 eps)))
1115 (right (adaptive-plot fcn b b1 c f-b f-b1 f-c (1- depth) (* 2 eps))))
1116 (append left (cddr right)))))))
1118 (defun adaptive-parametric-plot (x-fcn y-fcn a b c x-a x-b x-c y-a y-b y-c depth eps)
1119 ;; Step 1: Split the interval [a, c] into 5 points
1120 (let* ((a1 (/ (+ a b) 2))
1121 (b1 (/ (+ b c) 2))
1122 (x-a1 (funcall x-fcn a1))
1123 (x-b1 (funcall x-fcn b1))
1124 (y-a1 (funcall y-fcn a1))
1125 (y-b1 (funcall y-fcn b1)))
1126 (cond ((or (not (plusp depth))
1127 ;; Should we have a different algorithm to determine
1128 ;; slow oscillation and smooth-enough for parametric
1129 ;; plots?
1130 (and (slow-oscillation-p y-a y-a1 y-b y-b1 y-c)
1131 (slow-oscillation-p x-a x-a1 x-b x-b1 x-c)
1132 (smooth-enough-p y-a y-a1 y-b y-b1 y-c eps)
1133 (smooth-enough-p x-a x-a1 x-b x-b1 x-c eps)))
1134 ;; Everything is nice and smooth so we're done. Don't
1135 ;; refine anymore.
1136 (list x-a y-a
1137 x-a1 y-a1
1138 x-b y-b
1139 x-b1 y-b1
1140 x-c y-c))
1141 ;; We are not plotting the real part of the function and the
1142 ;; function is undefined at all points - assume it has complex value
1143 ;; on [a,b]. Maybe we should refine it a couple of times just to make sure?
1144 ((and (null *plot-realpart*)
1145 (null y-a) (null y-a1) (null y-b) (null y-b1) (null y-c)
1146 (null x-a) (null x-a1) (null x-b) (null x-b1) (null x-c))
1147 (list x-a y-a
1148 x-a1 y-a1
1149 x-b y-b
1150 x-b1 y-b1
1151 x-c y-c))
1153 ;; Need to refine. Split the interval in half, and try to plot each half.
1154 (let ((left (adaptive-parametric-plot x-fcn y-fcn
1155 a a1 b
1156 x-a x-a1 x-b
1157 y-a y-a1 y-b
1158 (1- depth) (* 2 eps)))
1159 (right (adaptive-parametric-plot x-fcn y-fcn
1160 b b1 c
1161 x-b x-b1 x-c
1162 y-b y-b1 y-c
1163 (1- depth) (* 2 eps))))
1164 ;; (cddr right) to skip over the point that is duplicated
1165 ;; between the right end-point of the left region and the
1166 ;; left end-point of the right
1167 (append left (cddr right)))))))
1169 (defun draw2d (fcn range plot-options)
1170 (if (and ($listp fcn) (equal '$parametric (cadr fcn)))
1171 (return-from draw2d
1172 (if $use_adaptive_parametric_plot
1173 (draw2d-parametric-adaptive fcn range plot-options)
1174 (draw2d-parametric fcn range plot-options))))
1175 (if (and ($listp fcn) (equal '$discrete (cadr fcn)))
1176 (return-from draw2d (draw2d-discrete fcn)))
1177 (let* ((nticks (getf plot-options :nticks))
1178 (yrange (getf plot-options :ybounds))
1179 (depth (getf plot-options :adapt_depth)))
1181 (setq fcn (coerce-float-fun fcn `((mlist), (second range))))
1183 (let* ((x-start (coerce-float (third range)))
1184 (xend (coerce-float (fourth range)))
1185 (x-step (/ (- xend x-start) (coerce-float nticks) 2))
1186 (ymin (coerce-float (first yrange)))
1187 (ymax (coerce-float (second yrange)))
1188 (n-clipped 0) (n-non-numeric 0)
1189 ;; What is a good EPS value for adaptive plotting?
1190 ;(eps 1e-5)
1191 x-samples y-samples result
1193 (declare (type flonum ymin ymax))
1194 ;; Divide the region into NTICKS regions. Each region has a
1195 ;; start, mid and endpoint. Then adaptively plot over each of
1196 ;; these regions. So it's probably a good idea not to make
1197 ;; NTICKS too big. Since adaptive plotting splits the sections
1198 ;; in half, it's also probably not a good idea to have NTICKS be
1199 ;; a power of two.
1200 (when (getf plot-options :logx)
1201 (setf x-start (log x-start))
1202 (setf xend (log xend))
1203 (setf x-step (/ (- xend x-start) (coerce-float nticks) 2)))
1205 (flet ((fun (x)
1206 (let ((y (if (getf plot-options :logx)
1207 (funcall fcn (exp x))
1208 (funcall fcn x))))
1209 (if (and (getf plot-options :logy)
1210 (numberp y))
1211 (if (> y 0) (log y) 'und)
1212 y))))
1214 (dotimes (k (1+ (* 2 nticks)))
1215 (let ((x (+ x-start (* k x-step))))
1216 (push x x-samples)
1217 (push (fun x) y-samples)))
1218 (setf x-samples (nreverse x-samples))
1219 (setf y-samples (nreverse y-samples))
1221 ;; For each region, adaptively plot it.
1222 (do ((x-start x-samples (cddr x-start))
1223 (x-mid (cdr x-samples) (cddr x-mid))
1224 (x-end (cddr x-samples) (cddr x-end))
1225 (y-start y-samples (cddr y-start))
1226 (y-mid (cdr y-samples) (cddr y-mid))
1227 (y-end (cddr y-samples) (cddr y-end)))
1228 ((null x-end))
1229 ;; The region is x-start to x-end, with mid-point x-mid.
1231 ;; The cddr is to remove the one extra sample (x and y value)
1232 ;; that adaptive plot returns. But on the first iteration,
1233 ;; result is empty, so we don't want the cddr because we want
1234 ;; all the samples returned from adaptive-plot. On subsequent
1235 ;; iterations, it's a duplicate of the last point of the
1236 ;; previous interval.
1237 (setf result
1238 (if result
1239 (append result
1240 (cddr
1241 (adaptive-plot #'fun (car x-start) (car x-mid) (car x-end)
1242 (car y-start) (car y-mid) (car y-end)
1243 depth 1e-5)))
1244 (adaptive-plot #'fun (car x-start) (car x-mid) (car x-end)
1245 (car y-start) (car y-mid) (car y-end)
1246 depth 1e-5))))
1248 ;; Fix up out-of-range values
1249 ;; and clobber non-numeric values.
1251 (do ((x result (cddr x))
1252 (y (cdr result) (cddr y)))
1253 ((null y))
1254 (if (numberp (car y))
1255 (unless (<= ymin (car y) ymax)
1256 (incf n-clipped)
1257 (setf (car x) 'moveto)
1258 (setf (car y) 'moveto))
1259 (progn
1260 (incf n-non-numeric)
1261 (setf (car x) 'moveto)
1262 (setf (car y) 'moveto)))
1263 (when (and (getf plot-options :logx)
1264 (numberp (car x)))
1265 (setf (car x) (exp (car x))))
1267 (when (and (getf plot-options :logy)
1268 (numberp (car y)))
1269 (setf (car y) (exp (car y)))))
1271 ;; Filter out any MOVETO's which do not precede a number.
1272 ;; Code elsewhere in this file expects MOVETO's to
1273 ;; come in pairs, so leave two MOVETO's before a number.
1274 (let ((n (length result)))
1275 (dotimes (i n)
1276 (when
1277 (and
1278 (evenp i)
1279 (eq (nth i result) 'moveto)
1280 (eq (nth (1+ i) result) 'moveto)
1281 (or
1282 (eq i (- n 2))
1283 (eq (nth (+ i 2) result) 'moveto)))
1284 (setf (nth i result) nil)
1285 (setf (nth (1+ i) result) nil))))
1287 (let ((result-sans-nil (delete nil result)))
1288 (if (null result-sans-nil)
1289 (cond
1290 ((= n-non-numeric 0)
1291 (mtell (intl:gettext "plot2d: all values were clipped.~%")))
1292 ((= n-clipped 0)
1293 (mtell (intl:gettext "plot2d: expression evaluates to non-numeric value everywhere in plotting range.~%")))
1295 (mtell (intl:gettext "plot2d: all values are non-numeric, or clipped.~%"))))
1296 (progn
1297 (if (> n-non-numeric 0)
1298 (mtell (intl:gettext "plot2d: expression evaluates to non-numeric value somewhere in plotting range.~%")))
1299 (if (> n-clipped 0)
1300 (mtell (intl:gettext "plot2d: some values were clipped.~%")))))
1301 (cons '(mlist) result-sans-nil))))))
1303 (defun get-range (lis)
1304 (let ((ymin most-positive-flonum)
1305 (ymax most-negative-flonum))
1306 (declare (type flonum ymin ymax))
1307 (do ((l lis (cddr l)))
1308 ((null l))
1309 (or (floatp (car l)) (setf (car l) (float (car l))))
1310 (cond ((< (car l) ymin)
1311 (setq ymin (car l))))
1312 (cond ((< ymax (car l))
1313 (setq ymax (car l)))))
1314 (list '(mlist) ymin ymax)))
1316 #+sbcl (defvar $gnuplot_view_args "-persist ~a")
1317 #-sbcl (defvar $gnuplot_view_args "-persist ~s")
1319 #+(or sbcl openmcl) (defvar $gnuplot_file_args "~a")
1320 #-(or sbcl openmcl) (defvar $gnuplot_file_args "~s")
1322 (defvar $mgnuplot_command "mgnuplot")
1323 (defvar $geomview_command "geomview")
1325 (defvar $xmaxima_plot_command "xmaxima")
1327 (defun plot-temp-file (file)
1328 (if *maxima-tempdir*
1329 (format nil "~a/~a" *maxima-tempdir* file)
1330 file))
1332 ;; If no file path is given, uses temporary directory path
1333 (defun plot-file-path (file)
1334 (if (pathname-directory file)
1335 file
1336 (plot-temp-file file)))
1338 (defun gnuplot-process (plot-options &optional file out-file)
1339 (let ((gnuplot-term (getf plot-options :gnuplot_term))
1340 (run-viewer (getf plot-options :run_viewer))
1341 (gnuplot-preamble
1342 (string-downcase (getf plot-options :gnuplot_preamble))))
1344 ;; creates the output file, when there is one to be created
1345 (when (and out-file (not (eq gnuplot-term '$default)))
1346 #+(or (and sbcl win32) (and ccl windows))
1347 ($system $gnuplot_command (format nil $gnuplot_file_args file))
1348 #-(or (and sbcl win32) (and ccl windows))
1349 ($system (format nil "~a ~a" $gnuplot_command
1350 (format nil $gnuplot_file_args file))))
1352 ;; displays contents of the output file, when gnuplot-term is dumb,
1353 ;; or runs gnuplot when gnuplot-term is default
1354 (when run-viewer
1355 (case gnuplot-term
1356 ($default
1357 ;; the options given to gnuplot will be different when the user
1358 ;; redirects the output by using "set output" in the preamble
1359 #+(or (and sbcl win32) (and ccl windows))
1360 ($system $gnuplot_command "-persist" (format nil $gnuplot_file_args file))
1361 #-(or (and sbcl win32) (and ccl windows))
1362 ($system
1363 (format nil "~a ~a" $gnuplot_command
1364 (format nil (if (search "set out" gnuplot-preamble)
1365 $gnuplot_file_args $gnuplot_view_args)
1366 file))))
1367 ($dumb
1368 (if out-file
1369 ($printfile (car out-file))
1370 (merror (intl:gettext "plotting: option 'gnuplot_out_file' not defined."))))))))
1372 ;; plot-options-parser puts the plot options given into a property list.
1373 ;; maxopts: a list (not a Maxima list!) with plot options.
1374 ;; options: a property list, or an empty list.
1375 ;; Example:
1376 ;; (plot-options-parser (list #$[x,-2,2]$ #$[nticks,30]$) '(:nticks 4))
1377 ;; returns:
1378 ;; (:XLABEL "x" :XMAX 2.0 :XMIN -2.0 :NTICKS 30)
1380 (defun plot-options-parser (maxopts options &aux name)
1381 (unless (every #'$listp maxopts)
1382 (setq maxopts
1383 (mapcar #'(lambda (x) (if ($listp x) x (list '(mlist) x))) maxopts)))
1384 (dolist (opt maxopts)
1385 (unless ($symbolp (setq name (second opt)))
1386 (merror
1387 (intl:gettext
1388 "plot-options-parser: Expecting a symbol for the option name, found: \"~M\"") opt))
1389 (case name
1390 ($adapt_depth
1391 (setf (getf options :adapt_depth)
1392 (check-option (cdr opt) #'naturalp "a natural number" 1)))
1393 ($axes (setf (getf options :axes)
1394 (check-option-b (cdr opt) #'axesoptionp "x, y, solid" 1)))
1395 ($azimuth (if (caddr opt)
1396 (setf (caddr opt) (parse-azimuth (caddr opt))))
1397 (setf (getf options :azimuth)
1398 (check-option (cdr opt) #'realp "a real number" 1)))
1399 ($box (setf (getf options :box)
1400 (check-option-boole (cdr opt))))
1401 ($color_bar_tics
1402 (if (cddr opt)
1403 (setf (cddr opt) (mapcar #'coerce-float (cddr opt))))
1404 (setf (getf options :color_bar_tics)
1405 (check-option-b (cdr opt) #'realp "a real number" 3)))
1406 ($color (setf (getf options :color)
1407 (check-option (cdr opt) #'plotcolorp "a color")))
1408 ($color_bar (setf (getf options :color_bar)
1409 (check-option-boole (cdr opt))))
1410 ($elevation (if (caddr opt)
1411 (setf (caddr opt) (parse-elevation (caddr opt))))
1412 (setf (getf options :elevation)
1413 (check-option (cdr opt) #'realp "a real number" 1)))
1414 ($grid (setf (getf options :grid)
1415 (check-option (cdr opt) #'naturalp "a natural number" 2)))
1416 ($grid2d (setf (getf options :grid2d)
1417 (check-option-boole (cdr opt))))
1418 ($iterations
1419 (setf (getf options :iterations)
1420 (check-option (cdr opt) #'naturalp "a natural number" 1)))
1421 ($label (setf (getf options :label)
1422 (check-option-label (cdr opt))))
1423 ($legend (setf (getf options :legend)
1424 (check-option-b (cdr opt) #'stringp "a string")))
1425 ($logx (setf (getf options :logx)
1426 (check-option-boole (cdr opt))))
1427 ($logy (setf (getf options :logy)
1428 (check-option-boole (cdr opt))))
1429 ($mesh_lines_color
1430 (setf (getf options :mesh_lines_color)
1431 (check-option-b (cdr opt) #'plotcolorp "a color" 1)))
1432 ($nticks (setf (getf options :nticks)
1433 (check-option (cdr opt) #'naturalp "a natural number" 1)))
1434 ($palette (setf (getf options :palette)
1435 (check-option-palette (cdr opt))))
1436 ($plot_format (setf (getf options :plot_format)
1437 (check-option-format (cdr opt))))
1438 ($plot_realpart (setf (getf options :plot_realpart)
1439 (check-option-boole (cdr opt))))
1440 ($point_type (setf (getf options :point_type)
1441 (check-option (cdr opt) #'pointtypep "a point type")))
1442 ($pdf_file (setf (getf options :pdf_file)
1443 (check-option (cdr opt) #'stringp "a string" 1)))
1444 ($png_file (setf (getf options :png_file)
1445 (check-option (cdr opt) #'stringp "a string" 1)))
1446 ($ps_file (setf (getf options :ps_file)
1447 (check-option (cdr opt) #'stringp "a string" 1)))
1448 ($run_viewer (setf (getf options :run_viewer)
1449 (check-option-boole (cdr opt))))
1450 ($same_xy (setf (getf options :same_xy)
1451 (check-option-boole (cdr opt))))
1452 ($same_xyz (setf (getf options :same_xyz)
1453 (check-option-boole (cdr opt))))
1454 ($style (setf (getf options :style)
1455 (check-option-style (cdr opt))))
1456 ($svg_file (setf (getf options :svg_file)
1457 (check-option (cdr opt) #'stringp "a string" 1)))
1458 ($t (setf (getf options :t) (cddr (check-range opt))))
1459 ($title (setf (getf options :title)
1460 (check-option (cdr opt) #'stringp "a string" 1)))
1461 ($transform_xy (setf (getf options :transform_xy)
1462 (check-option-b (cdr opt) #'functionp "a function make_transform" 1)))
1463 ($x (setf (getf options :x) (cddr (check-range opt))))
1464 ($xbounds (setf (getf options :xbounds) (cddr (check-range opt))))
1465 ($xlabel (setf (getf options :xlabel)
1466 (check-option (cdr opt) #'string "a string" 1)))
1467 ($xtics
1468 (if (cddr opt)
1469 (setf (cddr opt) (mapcar #'coerce-float (cddr opt))))
1470 (setf (getf options :xtics)
1471 (check-option-b (cdr opt) #'realp "a real number" 3)))
1472 ($xvar (setf (getf options :xvar)
1473 (check-option (cdr opt) #'string "a string" 1)))
1474 ($xy_scale
1475 (if (cddr opt)
1476 (setf (cddr opt) (mapcar #'coerce-float (cddr opt))))
1477 (setf (getf options :xy_scale)
1478 (check-option (cdr opt) #'realpositivep
1479 "a positive real number" 2)))
1480 ($y (setf (getf options :y) (cddr (check-range opt))))
1481 ($ybounds (setf (getf options :ybounds) (cddr (check-range opt))))
1482 ($ylabel (setf (getf options :ylabel)
1483 (check-option (cdr opt) #'string "a string" 1)))
1484 ($ytics
1485 (if (cddr opt)
1486 (setf (cddr opt) (mapcar #'coerce-float (cddr opt))))
1487 (setf (getf options :ytics)
1488 (check-option-b (cdr opt) #'realp "a real number" 3)))
1489 ($yvar (setf (getf options :yvar)
1490 (check-option (cdr opt) #'string "a string" 1)))
1491 ($yx_ratio
1492 (if (caddr opt)
1493 (setf (caddr opt) (coerce-float (caddr opt))))
1494 (setf (getf options :yx_ratio)
1495 (check-option (cdr opt) #'realp "a real number" 1)))
1496 ($z (setf (getf options :z) (cddr (check-range opt))))
1497 ($zlabel (setf (getf options :zlabel)
1498 (check-option (cdr opt) #'string "a string" 1)))
1499 ($zmin
1500 (if (caddr opt)
1501 (setf (caddr opt) (coerce-float (caddr opt))))
1502 (setf (getf options :zmin)
1503 (check-option-b (cdr opt) #'realp "a real number" 1)))
1504 ($ztics
1505 (if (cddr opt)
1506 (setf (cddr opt) (mapcar #'coerce-float (cddr opt))))
1507 (setf (getf options :ztics)
1508 (check-option-b (cdr opt) #'realp "a real number" 3)))
1509 ($gnuplot_4_0 (setf (getf options :gnuplot_4_0)
1510 (check-option-boole (cdr opt))))
1511 ($gnuplot_curve_titles
1512 (setf (getf options :gnuplot_curve_titles)
1513 (check-option (cdr opt) #'stringp "a string")))
1514 ($gnuplot_curve_styles
1515 (setf (getf options :gnuplot_curve_styles)
1516 (check-option (cdr opt) #'stringp "a string")))
1517 ($gnuplot_default_term_command
1518 (setf (getf options :gnuplot_default_term_command)
1519 (check-option (cdr opt) #'stringp "a string" 1)))
1520 ($gnuplot_dumb_term_command
1521 (setf (getf options :gnuplot_dumb_term_command)
1522 (check-option (cdr opt) #'stringp "a string" 1)))
1523 ($gnuplot_out_file
1524 (setf (getf options :gnuplot_out_file)
1525 (check-option (cdr opt) #'stringp "a string" 1)))
1526 ($gnuplot_pm3d
1527 (setf (getf options :gnuplot_pm3d)
1528 (check-option-boole (cdr opt))))
1529 ($gnuplot_preamble
1530 (setf (getf options :gnuplot_preamble)
1531 (check-option (cdr opt) #'stringp "a string" 1)))
1532 ($gnuplot_postamble
1533 (setf (getf options :gnuplot_postamble)
1534 (check-option (cdr opt) #'stringp "a string" 1)))
1535 ($gnuplot_pdf_term_command
1536 (setf (getf options :gnuplot_pdf_term_command)
1537 (check-option (cdr opt) #'stringp "a string" 1)))
1538 ($gnuplot_png_term_command
1539 (setf (getf options :gnuplot_png_term_command)
1540 (check-option (cdr opt) #'stringp "a string" 1)))
1541 ($gnuplot_ps_term_command
1542 (setf (getf options :gnuplot_ps_term_command)
1543 (check-option (cdr opt) #'stringp "a string" 1)))
1544 ($gnuplot_svg_term_command
1545 (setf (getf options :gnuplot_svg_term_command)
1546 (check-option (cdr opt) #'stringp "a string" 1)))
1547 ;; gnuplot_term is a tricky one: when it is just default, dumb or
1548 ;; ps, we want it to be a symbol, but when it is more complicated,
1549 ;; i.e. "ps; size 16cm, 12cm", it must be a string and not a symbol
1550 ($gnuplot_term
1551 (let ((s (caddr opt)))
1552 (when (stringp s)
1553 (cond ((string= s "default") (setq s '$default))
1554 ((string= s "dumb") (setq s '$dumb))
1555 ((string= s "ps") (setq s '$ps))))
1556 (if (atom s)
1557 (setf (getf options :gnuplot_term) s)
1558 (merror
1559 (intl:gettext "Wrong argument for plot option \"gnuplot_term\". Expecting a string or a symbol but found \"~M\".") s))))
1561 (merror
1562 (intl:gettext "plot-options-parser: unknown plot option: ~M") opt))))
1564 ;; plots that create a file work better in gnuplot than gnuplot_pipes
1565 (when (and (eq (getf options :plot_format) '$gnuplot_pipes)
1566 (or (eq (getf options :gnuplot_term) '$dumb)
1567 (getf options :pdf_file) (getf options :png_file)
1568 (getf options :ps_file) (getf options :svg_file)))
1569 (setf (getf options :plot_format) '$gnuplot))
1571 options)
1573 ;; natural numbers predicate
1574 (defun naturalp (n) (or (and (integerp n) (> n 0)) nil))
1576 ;; positive real numbers predicate
1577 (defun realpositivep (x) (or (and (realp x) (> x 0)) nil))
1579 ;; posible values for the axes option
1580 (defun axesoptionp (o) (if (member o '($x $y $solid)) t nil))
1582 ;; the 13 possibilities for the point types
1583 (defun pointtypep (p)
1584 (if (member p '($bullet $circle $plus $times $asterisk $box $square
1585 $triangle $delta $wedge $nabla $diamond $lozenge)) t nil))
1587 ;; Colors can only one of the named colors or a six-digit hexadecimal
1588 ;; number with a # suffix.
1589 (defun plotcolorp (color)
1590 (cond ((and (stringp color)
1591 (string= (subseq color 0 1) "#")
1592 (= (length color) 7)
1593 (ignore-errors (parse-integer (subseq color 1 6) :radix 16)))
1595 ((member color '($red $green $blue $magenta $cyan $yellow
1596 $orange $violet $brown $gray $black $white))
1598 (t nil)))
1600 ;; tries to convert az into a floating-point number between 0 and 360
1601 (defun parse-azimuth (az) (mod ($float (meval* az)) 360))
1603 ;; tries to convert el into a floating-poitn number between -180 and 180
1604 (defun parse-elevation (el) (- (mod (+ 180 ($float (meval* el))) 360) 180))
1606 ;; The following functions check the value of an option returning an atom
1607 ;; when there is only one argument or a list when there are several arguments
1610 ;; Checks for one or more items of the same type, using the test given
1611 (defun check-option (option test type &optional count)
1612 (when count
1613 (unless (= (1- (length option)) count)
1614 (merror
1615 (intl:gettext
1616 "Wrong number of arguments for plot option \"~M\". Expecting ~M but found ~M.")
1617 (car option) count (1- (length option)))))
1618 (dolist (item (cdr option))
1619 (when (not (funcall test item))
1620 (merror
1621 (intl:gettext "Wrong argument for plot option \"~M\". Expecting ~M but found \"~M\".") (car option) type item)))
1622 (if (= (length option) 2)
1623 (cadr option)
1624 (cdr option)))
1626 ;; Accepts one or more items of the same type or true or false.
1627 ;; When given, n is the maximum number of items.
1628 (defun check-option-b (option test type &optional count)
1629 (let ((n (- (length option) 1)))
1630 (when count
1631 (unless (< n (1+ count))
1632 (merror
1633 (intl:gettext
1634 "Wrong number of arguments for plot option \"~M\". Expecting ~M but found ~M.")
1635 (car option) count (1- (length option)))))
1636 (cond
1637 ((= n 0) t)
1638 ((= n 1) (if (or (funcall test (cadr option)) (null (cadr option))
1639 (eq (cadr option) t))
1640 (cadr option)
1641 (merror (intl:gettext "Wrong argument for plot option \"~M\". Expecting ~M, true or false but found \"~M\".") (car option) type (cadr option))))
1642 ((> n 1)
1643 (dotimes (i n)
1644 (unless (funcall test (nth (+ i 1) option))
1645 (merror
1646 (intl:gettext "Wrong argument for plot option \"~M\". Expecting ~M but found \"~M\".") (car option) type (nth (+ i 1) option))))
1647 (cdr option)))))
1649 ;; Boolean options can be [option], [option,true] or [option,false]
1650 (defun check-option-boole (option)
1651 (if (= 1 (length option))
1653 (if (and (= 2 (length option))
1654 (or (eq (cadr option) t) (null (cadr option))))
1655 (cadr option)
1656 (merror (intl:gettext "plot option ~M must be either true or false.")
1657 (car option)))))
1659 ;; label can be either [label, string, real, real] or
1660 ;; [label, [string_1, real, real],...,[string_n, real, real]]
1661 (defun check-option-label (option &aux opt)
1662 (if (not ($listp (cadr option)))
1663 (setq opt (list (cons '(mlist) (cdr option))))
1664 (setq opt (cdr option)))
1665 (dolist (item opt)
1666 (when (not (and ($listp item) (= 4 (length item)) (stringp (second item))
1667 (realp (setf (third item) (coerce-float (third item))))
1668 (realp (setf (fourth item) (coerce-float (fourth item))))))
1669 (merror
1670 (intl:gettext
1671 "Wrong argument ~M for option ~M. Must be either [label,\"text\",x,y] or [label, [\"text 1\",x1,y1],...,[\"text n\",xn,yn]]")
1672 item (car option))))
1673 opt)
1675 ;; one of the possible formats
1676 (defun check-option-format (option &aux formats)
1677 (if (string= *autoconf-win32* "true")
1678 (setq formats '($geomview $gnuplot $mgnuplot $openmath $xmaxima))
1679 (setq formats '($geomview $gnuplot $gnuplot_pipes $mgnuplot $openmath $xmaxima)))
1680 (unless (member (cadr option) formats)
1681 (merror
1682 (intl:gettext
1683 "Wrong argument ~M for option ~M. Must one of the following symbols: geomview, gnuplot, mgnuplot, xmaxima (or gnuplot_pipes in Unix)")
1684 (cadr option) (car option)))
1685 ; $openmath is just a synonym for $xmaxima
1686 (if (eq (cadr option) '$openmath)
1687 '$xmaxima
1688 (cadr option)))
1690 ; palette most be one or more Maxima lists starting with the name of one
1691 ;; of the 5 kinds: hue, saturation, value, gray or gradient.
1692 (defun check-option-palette (option)
1693 (if (and (= (length option) 2) (null (cadr option)))
1695 (progn
1696 (dolist (item (cdr option))
1697 (when (not (and ($listp item)
1698 (member (cadr item)
1699 '($hue $saturation $value $gray $gradient))))
1700 (merror
1701 (intl:gettext
1702 "Wrong argument ~M for option ~M. Not a valid palette.")
1703 item (car option))))
1704 (cdr option))))
1706 ;; style can be one or several of the names of the styles or one or several
1707 ;; Maxima lists starting with the name of one of the styles.
1708 (defun check-option-style (option)
1709 (if (not ($listp (cadr option)))
1710 (dolist (item (rest option))
1711 (when (not (member item
1712 '($lines $points $linespoints $dots $impulses)))
1713 (merror
1714 (intl:gettext
1715 "Wrong argument ~M for option ~M. Not a valid style")
1716 item (car option))))
1717 (dolist (item (rest option))
1718 (when (not
1719 (and ($listp item)
1720 (member (cadr item)
1721 '($lines $points $linespoints $dots $impulses))))
1722 (merror
1723 (intl:gettext
1724 "Wrong argument ~M for option ~M. Not a valid style.")
1725 item (car option)))))
1726 (if (= (length option) 2)
1727 (cadr option)
1728 (cdr option)))
1730 ;; Transform can be false or the name of a function for the transformation.
1731 (defun check-option-transform (option)
1732 (if (and (= (length option) 2)
1733 (or (atom (cadr option)) (null (cadr option))))
1734 (cadr option)
1735 (merror
1736 (intl:gettext
1737 "Wrong argument ~M for option ~M. Should be either false or the name of function for the transformation") option (car option))))
1739 ;; plot2d
1741 ;; Examples:
1742 ;; plot2d (sec(x), [x, -2, 2], [y, -20, 20], [nticks, 200])$
1744 ;; plot2d (exp(3*s), [s, -2, 2], [logy])$
1746 ;; plot2d ([parametric, cos(t), sin(t), [t, -%pi, %pi], [nticks, 80]],
1747 ;; [x, -4/3, 4/3])$
1749 ;; xy:[[10,.6], [20,.9], [30,1.1], [40,1.3], [50,1.4]]$
1750 ;; plot2d ( [ [discrete, xy], 2*%pi*sqrt(l/980) ], [l, 0, 50],
1751 ;; [style, points, lines], [color, red, blue], [point_type, box],
1752 ;; [legend, "experiment", "theory"],
1753 ;; [xlabel, "pendulum's length (cm)"], [ylabel, "period (s)"])$
1755 ;; plot2d ( x^2-1, [x, -3, 3], [y, -2, 10], [box, false], [color, red],
1756 ;; [ylabel, "x^2-1"], [plot_format, xmaxima])$
1758 (defun $plot2d (fun &optional range &rest extra-options)
1759 (let (($display2d nil) (*plot-realpart* *plot-realpart*)
1760 (options (copy-tree *plot-options*)) (i 0)
1761 output-file gnuplot-term gnuplot-out-file file points-lists)
1763 ;; 1- Put fun in its most general form: a maxima list with several objects
1764 ;; that can be expressions (simple functions) and maxima lists (parametric
1765 ;; functions or discrete sets of points).
1767 ;; If there is a single parametric function use its range as the range for
1768 ;; the plot and put it inside another maxima list
1769 (setf (getf options :type) "plot2d")
1770 (when (and (consp fun) (eq (second fun) '$parametric))
1771 (unless range
1772 (setq range (check-range (nth 4 fun))))
1773 (setq fun `((mlist) ,fun)))
1775 ;; If there is a single set of discrete points put it inside a maxima list
1776 (when (and (consp fun) (eq (second fun) '$discrete))
1777 (setq fun `((mlist) ,fun)))
1779 ;; If at this point fun is not a maxima list, it is then a single function
1780 (unless ($listp fun ) (setq fun `((mlist) ,fun)))
1782 ;; 2- Get names for the two axis and values for xmin and xmax if needed.
1784 ;; If any of the objects in the fun list is a simple function,
1785 ;; the range option is mandatory and will provide the name of
1786 ;; the horizontal axis and the values of xmin and xmax.
1787 (let ((no-range-required t) small huge)
1788 #-clisp (setq small (- (/ most-positive-flonum 1024)))
1789 #+clisp (setq small (- (/ most-positive-double-float 1024.0)))
1790 #-clisp (setq huge (/ most-positive-flonum 1024))
1791 #+clisp (setq huge (/ most-positive-double-float 1024.0))
1792 (setf (getf options :ybounds) (list small huge))
1793 (dolist (subfun (rest fun))
1794 (if (not ($listp subfun))
1795 (setq no-range-required nil)))
1796 (unless no-range-required
1797 (setq range (check-range range))
1798 (setf (getf options :xlabel) (ensure-string (second range)))
1799 (setf (getf options :x) (cddr range)))
1800 (when no-range-required
1801 ;; Make the default ranges on X nd Y large so parametric plots
1802 ;; don't get prematurely clipped. Don't use most-positive-flonum
1803 ;; because draw2d will overflow.
1804 (setf (getf options :xbounds) (list small huge))
1805 (when range
1806 ;; second argument was really a plot option, not a range
1807 (setq extra-options (cons range extra-options)))))
1809 ;; When only one function is being plotted:
1810 ;; If a simple function use, its name for the vertical axis.
1811 ;; If parametric, give the axes the names of the two parameters.
1812 ;; If discrete points, name the axes x and y.
1813 (when (= (length fun) 2)
1814 (let ((v (second fun)) label)
1815 (cond ((atom v)
1816 (setq label (coerce (mstring v) 'string))
1817 (if (< (length label) 80)
1818 (setf (getf options :ylabel) label)))
1819 ((eq (second v) '$parametric)
1820 (setq label (coerce (mstring (third v)) 'string))
1821 (if (< (length label) 80)
1822 (setf (getf options :xlabel) label))
1823 (setq label (coerce (mstring (fourth v)) 'string))
1824 (if (< (length label) 80)
1825 (setf (getf options :ylabel) label)))
1826 ((eq (second v) '$discrete)
1827 (setf (getf options :xlabel) "x")
1828 (setf (getf options :ylabel) "y"))
1830 (setq label (coerce (mstring v) 'string))
1831 (if (< (length label) 80)
1832 (setf (getf options :ylabel) label))))))
1834 ;; Parse the given options into the options list
1835 (setq options (plot-options-parser extra-options options))
1836 (when (getf options :y) (setf (getf options :ybounds) (getf options :y)))
1838 ;; Remove axes labels when no box is used in gnuplot
1839 (when (and (member :box options) (not (getf options :box))
1840 (not (eq (getf options :plot_format) '$xmaxima)))
1841 (remf options :xlabel)
1842 (remf options :ylabel))
1845 (let ((xmin (first (getf options :x))) (xmax (second (getf options :x))))
1846 (when
1847 (and (getf options :logx) xmin xmax)
1848 (if (> xmax 0)
1849 (when (<= xmin 0)
1850 (let ((revised-xmin (/ xmax 1000)))
1851 (mtell (intl:gettext "plot2d: lower bound must be positive when 'logx' in effect.~%plot2d: assuming lower bound = ~M instead of ~M") revised-xmin xmin)
1852 (setf (getf options :x) (list revised-xmin xmax))
1853 (setq range `((mlist) ,(second range) ,revised-xmin ,xmax))))
1854 (merror (intl:gettext "plot2d: upper bound must be positive when 'logx' in effect; found: ~M") xmax))))
1856 (let ((ymin (first (getf options :y)))
1857 (ymax (second (getf options :y))))
1858 (when (and (getf options :logy) ymin ymax)
1859 (if (> ymax 0)
1860 (when (<= ymin 0)
1861 (let ((revised-ymin (/ ymax 1000)))
1862 (mtell (intl:gettext "plot2d: lower bound must be positive when 'logy' in effect.~%plot2d: assuming lower bound = ~M instead of ~M") revised-ymin ymin)
1863 (setf (getf options :y) (list revised-ymin ymax))))
1864 (merror (intl:gettext "plot2d: upper bound must be positive when 'logy' in effect; found: ~M") ymax))))
1866 (setq *plot-realpart* (getf options :plot_realpart))
1868 ;; Compute points to plot for each element of FUN.
1869 ;; If no plottable points are found, return immediately from $PLOT2D.
1871 (setq points-lists
1872 (mapcar #'(lambda (f) (cdr (draw2d f range options))) (cdr fun)))
1873 (when (= (count-if #'(lambda (x) x) points-lists) 0)
1874 (mtell (intl:gettext "plot2d: nothing to plot.~%"))
1875 (return-from $plot2d))
1877 (setq gnuplot-term (getf options :gnuplot_term))
1878 (setf gnuplot-out-file (getf options :gnuplot_out_file))
1879 (if (and (find (getf options :plot_format) '($gnuplot_pipes $gnuplot))
1880 (eq gnuplot-term '$default) gnuplot-out-file)
1881 (setq file (plot-file-path gnuplot-out-file))
1882 (setq file
1883 (plot-file-path
1884 (format nil "maxout.~(~a~)"
1885 (ensure-string (getf options :plot_format))))))
1887 ;; old function $plot2dopen incorporated here
1888 (case (getf options :plot_format)
1889 ($xmaxima
1890 (when (getf options :ps_file)
1891 (setq output-file (list (getf options :ps_file))))
1892 (show-open-plot
1893 (with-output-to-string
1894 (st)
1895 (xmaxima-print-header st options)
1896 (let ((legend (getf options :legend))
1897 (colors (getf options :color))
1898 (styles (getf options :style)) style plot-name)
1899 (unless (listp legend) (setq legend (list legend)))
1900 (unless (listp colors) (setq colors (list colors)))
1901 (unless (listp styles) (setq styles (list styles)))
1902 (loop for f in (cdr fun) for points-list in points-lists do
1903 (when points-list
1904 (if styles
1905 (progn
1906 (setq style (nth (mod i (length styles)) styles))
1907 (setq style (if ($listp style) (cdr style) `(,style))))
1908 (setq style nil))
1909 (incf i)
1910 (if (member :legend options)
1911 ;; a legend has been given in the options
1912 (setq plot-name
1913 (if (first legend)
1914 (ensure-string
1915 (nth (mod (- i 1) (length legend)) legend))
1916 nil)) ; no legend if option [legend,false]
1917 (if (= 2 (length fun))
1918 (progn
1919 (setq plot-name nil) ;no legend for single function
1920 (format st " {nolegend 1}"))
1921 (setq plot-name
1922 (let ((string ""))
1923 (cond
1924 ((atom f)
1925 (setq
1926 string (coerce (mstring f) 'string)))
1927 ((eq (second f) '$parametric)
1928 (setq
1929 string
1930 (concatenate
1931 'string
1932 (coerce (mstring (third f)) 'string)
1933 ", " (coerce (mstring (fourth f)) 'string))))
1934 ((eq (second f) '$discrete)
1935 (setq string
1936 (format nil "discrete~a" i)))
1938 (setq string
1939 (coerce (mstring f) 'string))))
1940 (cond ((< (length string) 80) string)
1941 (t (format nil "fun~a" i)))))))
1942 (when plot-name
1943 (format st " {label ~s}" plot-name))
1944 (format st " ~a~%" (xmaxima-curve-style style colors i))
1945 (format st " {xversusy~%")
1946 (let ((lis points-list))
1947 (loop while lis
1949 (loop while (and lis (not (eq (car lis) 'moveto)))
1950 collecting (car lis) into xx
1951 collecting (cadr lis) into yy
1952 do (setq lis (cddr lis))
1953 finally
1954 ;; only output if at least two points for line
1955 (when (cdr xx)
1956 (tcl-output-list st xx)
1957 (tcl-output-list st yy)))
1958 ;; remove the moveto
1959 (setq lis (cddr lis))))
1960 (format st "}"))))
1961 (format st "} "))
1962 file)
1963 (cons '(mlist) (cons file output-file)))
1965 (with-open-file (st file :direction :output :if-exists :supersede)
1966 (case (getf options :plot_format)
1967 ($gnuplot
1968 (setq output-file (gnuplot-print-header st options))
1969 (format st "plot")
1970 (when (getf options :x)
1971 (format st " [~{~,8f~^ : ~}]" (getf options :x)))
1972 (when (getf options :y)
1973 (unless (getf options :x)
1974 (format st " []"))
1975 (format st " [~{~,8f~^ : ~}]" (getf options :y))))
1976 ($gnuplot_pipes
1977 (check-gnuplot-process)
1978 ($gnuplot_reset)
1979 (setq output-file (gnuplot-print-header *gnuplot-stream* options))
1980 (setq *gnuplot-command* (format nil "plot"))
1981 (when (getf options :x)
1982 (setq
1983 *gnuplot-command*
1984 ($sconcat
1985 *gnuplot-command*
1986 (format nil " [~{~,8f~^ : ~}]" (getf options :x)))))
1987 (when (getf options :y)
1988 (unless (getf options :x)
1989 (setq *gnuplot-command*
1990 ($sconcat *gnuplot-command* (format nil " []"))))
1991 (setq
1992 *gnuplot-command*
1993 ($sconcat
1994 *gnuplot-command*
1995 (format nil " [~{~,8f~^ : ~}]" (getf options :y)))))))
1996 (let ((legend (getf options :legend))
1997 (colors (getf options :color))
1998 (types (getf options :point_type))
1999 (styles (getf options :style))
2000 style plot-name)
2001 (unless (listp legend) (setq legend (list legend)))
2002 (unless (listp colors) (setq colors (list colors)))
2003 (unless (listp styles) (setq styles (list styles)))
2004 (loop for v in (cdr fun) for points-list in points-lists do
2005 (when points-list
2006 (case (getf options :plot_format)
2007 ($gnuplot_pipes
2008 (if (> i 0)
2009 (setq *gnuplot-command*
2010 ($sconcat *gnuplot-command* ", ")))
2011 (setq *gnuplot-command*
2012 ($sconcat *gnuplot-command*
2013 (format nil "~s index ~a " file i)))))
2014 (if styles
2015 (setq style (nth (mod i (length styles)) styles))
2016 (setq style nil))
2017 (when ($listp style) (setq style (cdr style)))
2018 (incf i)
2019 (if (member :legend options)
2020 ;; a legend has been defined in the options
2021 (setq plot-name
2022 (if (first legend)
2023 (ensure-string
2024 (nth (mod (- i 1) (length legend)) legend))
2025 nil)) ; no legend if option [legend,false]
2026 (if (= 2 (length fun))
2027 (setq plot-name nil) ; no legend if just one function
2028 (setq plot-name
2029 (let ((string ""))
2030 (cond ((atom v)
2031 (setq string
2032 (coerce (mstring v) 'string)))
2033 ((eq (second v) '$parametric)
2034 (setq
2035 string
2036 (concatenate
2037 'string
2038 (coerce (mstring (third v)) 'string)
2039 ", "
2040 (coerce (mstring (fourth v)) 'string))))
2041 ((eq (second v) '$discrete)
2042 (setq
2043 string (format nil "discrete~a" i)))
2045 (setq string
2046 (coerce (mstring v) 'string))))
2047 (cond ((< (length string) 80) string)
2048 (t (format nil "fun~a" i)))))))
2049 (case (getf options :plot_format)
2050 ($gnuplot
2051 (when (> i 1) (format st ","))
2052 (format st " '-'")
2053 (if plot-name
2054 (format st " title ~s" plot-name)
2055 (format st " notitle"))
2056 (format st " ~a"
2057 (gnuplot-curve-style style colors types i)))
2058 ($gnuplot_pipes
2059 (setq *gnuplot-command*
2060 ($sconcat
2061 *gnuplot-command*
2062 (if plot-name
2063 (format
2064 nil " title ~s ~a" plot-name
2065 (gnuplot-curve-style style colors types i))
2066 (format
2067 nil " notitle ~a"
2068 (gnuplot-curve-style style colors types i)
2069 )))))))))
2070 (case (getf options :plot_format)
2071 ($gnuplot
2072 (format st "~%"))
2073 ($gnuplot_pipes
2074 (format st "~%")))
2075 (setq i 0)
2076 (loop for v in (cdr fun) for points-list in points-lists do
2077 (when points-list
2078 (incf i)
2079 (case (getf options :plot_format)
2080 ($gnuplot
2081 (if (> i 1)
2082 (format st "e~%")))
2083 ($gnuplot_pipes
2084 (if (> i 1)
2085 (format st "~%~%")))
2086 ($mgnuplot
2087 (format st "~%~%# \"Fun~a\"~%" i))
2089 (let (in-discontinuity points)
2090 (loop for (v w) on points-list by #'cddr
2092 (cond ((eq v 'moveto)
2093 (cond
2094 ((find (getf options :plot_format) '($gnuplot_pipes $gnuplot))
2095 ;; A blank line means a discontinuity
2096 (if (null in-discontinuity)
2097 (progn
2098 (format st "~%")
2099 (setq in-discontinuity t))))
2100 ((equal (getf options :plot_format) '$mgnuplot)
2101 ;; A blank line means a discontinuity
2102 (format st "~%"))
2104 (format st "move "))))
2105 (t (format st "~g ~g ~%" v w)
2106 (setq points t)
2107 (setq in-discontinuity nil))))
2108 (if (and (null points)
2109 (first (getf options :x))
2110 (first (getf options :y)))
2111 (format
2112 st "~,8f ~,8f ~%"
2113 (first (getf options :x))
2114 (first (getf options :y))))))))))
2116 (case (getf options :plot_format)
2117 ($gnuplot
2118 (gnuplot-process options file output-file))
2119 ($gnuplot_pipes
2120 (send-gnuplot-command *gnuplot-command*))
2121 ($mgnuplot
2122 ($system (concatenate 'string *maxima-plotdir* "/" $mgnuplot_command)
2123 (format nil " -plot2d ~s -title ~s" file "Fun1"))))
2124 (cons '(mlist) (cons file output-file))))
2127 (defun msymbolp (x)
2128 (and (symbolp x) (char= (char (symbol-value x) 0) #\$)))
2131 (defun $tcl_output (lis i &optional (skip 2))
2132 (when (not (typep i 'fixnum))
2133 (merror
2134 (intl:gettext "tcl_ouput: second argument must be an integer; found ~M")
2136 (when (not ($listp lis))
2137 (merror
2138 (intl:gettext "tcl_output: first argument must be a list; found ~M") lis))
2139 (format *standard-output* "~% {")
2140 (cond (($listp (second lis))
2141 (loop for v in lis
2143 (format *standard-output* "~,10g " (nth i v))))
2145 (setq lis (nthcdr i lis))
2146 (loop with v = lis while v
2148 (format *standard-output* "~,10g " (car v))
2149 (setq v (nthcdr skip v)))))
2150 (format *standard-output* "~% }"))
2151 (defun tcl-output-list ( st lis )
2152 (cond ((null lis) )
2153 ((atom (car lis))
2154 (princ " { " st)
2155 (loop for v in lis
2156 count t into n
2157 when (eql 0 (mod n 5))
2158 do (terpri st)
2160 (format st "~,10g " v))
2161 (format st " }~%"))
2162 (t (tcl-output-list st (car lis))
2163 (tcl-output-list st (cdr lis)))))
2165 (defun check-range (range &aux tem a b)
2166 (or (and ($listp range)
2167 (setq tem (cdr range))
2168 (or (symbolp (car tem)) ($subvarp (car tem)))
2169 (numberp (setq a ($float (meval* (second tem)))))
2170 (numberp (setq b ($float (meval* (third tem)))))
2171 (< a b))
2172 (if range
2173 (merror
2174 (intl:gettext "plotting: range must be of the form [variable, min, max]; found: ~M")
2175 range)
2176 (merror
2177 (intl:gettext "plotting: no range given; must supply range of the form [variable, min, max]"))))
2178 `((mlist) ,(car tem) ,(float a) ,(float b)))
2180 (defun $zero_fun (x y) x y 0.0)
2182 (defun output-points (pl &optional m)
2183 "If m is supplied print blank line every m lines"
2184 (let ((j -1))
2185 (declare (fixnum j))
2186 (loop for i below (length (polygon-pts pl))
2187 with ar = (polygon-pts pl)
2188 do (print-pt (aref ar i))
2189 (setq i (+ i 1))
2190 (print-pt (aref ar i))
2191 (setq i (+ i 1))
2192 (print-pt (aref ar i))
2193 (terpri $pstream)
2194 (cond (m
2195 (setq j (+ j 1))
2196 (cond ((eql j (the fixnum m))
2197 (terpri $pstream)
2198 (setq j -1)))))
2201 (defun output-points-tcl (dest pl m)
2202 (format dest " {matrix_mesh ~%")
2203 ;; x y z are done separately:
2204 (loop for off from 0 to 2
2205 with ar = (polygon-pts pl)
2206 with i of-type fixnum = 0
2207 do (setq i off)
2208 (format dest "~%{")
2209 (loop
2210 while (< i (length ar))
2211 do (format dest "~% {")
2212 (loop for j to m
2213 do (print-pt (aref ar i))
2214 (setq i (+ i 3)))
2215 (format dest "}~%"))
2216 (format dest "}~%"))
2217 (format dest "}~%"))
2219 (defun show-open-plot (ans file)
2220 (cond ($show_openplot
2221 (with-open-file (st1 (plot-temp-file "maxout.xmaxima") :direction :output :if-exists :supersede)
2222 (princ ans st1))
2223 ($system (concatenate 'string *maxima-prefix*
2224 (if (string= *autoconf-win32* "true") "\\bin\\" "/bin/")
2225 $xmaxima_plot_command)
2226 #-(or (and sbcl win32) (and ccl windows))
2227 (format nil " ~s &" file)
2228 #+(or (and sbcl win32) (and ccl windows))
2229 file))
2230 (t (princ ans) "")))
2233 ;; contour_plot -- set some parameters for Gnuplot and punt to plot3d
2235 ;; We go to some trouble here to avoid clobbering the Gnuplot preamble
2236 ;; specified by the user, either as a global option (via set_plot_option)
2237 ;; or specified in arguments to contour_plot. Just append or prepend
2238 ;; the parameters for contour plotting to the user-specified preamble.
2239 ;; Assume that arguments take precedence over global options.
2241 ;; contour_plot knows how to set parameters only for Gnuplot.
2242 ;; If the plot_format is not a Gnuplot format, complain.
2244 ;; Examples:
2246 ;; contour_plot (x^2 + y^2, [x, -4, 4], [y, -4, 4]);
2247 ;; contour_plot (sin(y) * cos(x)^2, [x, -4, 4], [y, -4, 4]);
2248 ;; F(x, y) := x^3 + y^2;
2249 ;; contour_plot (F, [u, -4, 4], [v, -4, 4]);
2250 ;; contour_plot (F, [u, -4, 4], [v, -4, 4], [gnuplot_preamble, "set size ratio -1"]);
2251 ;; set_plot_option ([gnuplot_preamble, "set cntrparam levels 12"]);
2252 ;; contour_plot (F, [u, -4, 4], [v, -4, 4]);
2253 ;; set_plot_option ([plot_format, xmaxima]);
2254 ;; contour_plot (F, [u, -4, 4], [v, -4, 4]); => error: must be gnuplot format
2255 ;; contour_plot (F, [u, -4, 4], [v, -4, 4], [plot_format, gnuplot]);
2257 (defun $contour_plot (expr &rest optional-args)
2258 (let*
2259 ((plot-format-in-options (getf *plot-options* :plot_format))
2260 (plot-format-in-arguments
2261 (caddar (member '$plot_format optional-args :key #'cadr)))
2262 (preamble-in-plot-options (getf *plot-options* :gnuplot_preamble))
2263 (preamble-in-arguments
2264 (caddar (member '$gnuplot_preamble optional-args :key #'cadr)))
2265 (contour-preamble "set contour; unset surface; set view map")
2266 (gnuplot-formats '($gnuplot $gnuplot_pipes))
2267 preamble)
2268 ;; Ensure that plot_format is some gnuplot format.
2269 ;; Argument takes precedence over global option.
2271 (if (or
2272 (and plot-format-in-arguments
2273 (not (member plot-format-in-arguments gnuplot-formats :test #'eq)))
2274 (and (not plot-format-in-arguments)
2275 (not (member plot-format-in-options gnuplot-formats :test #'eq))))
2276 (progn
2277 (mtell(intl:gettext "contour_plot: plot_format = ~a not recognized; must be a gnuplot format.~%")
2278 (ensure-string (or plot-format-in-arguments plot-format-in-options)))
2279 (return-from $contour_plot)))
2281 ;; Prepend contour preamble to preamble in arguments (if given)
2282 ;; and pass concatenated preamble as an argument to plot3d.
2283 ;; Otherwise if there is a global option preamble,
2284 ;; append contour preamble to global option preamble.
2285 ;; Otherwise just set global option preamble to the contour preamble.
2287 ;; All this complication is to avoid clobbering the preamble
2288 ;; if one was specified somehow (either global option or argument).
2290 (if preamble-in-arguments
2291 (progn
2292 (setq optional-args
2293 (remove-if #'(lambda (e) (and ($listp e) (eq ($first e) '$gnuplot_preamble))) optional-args))
2294 (setq preamble
2295 ($sconcat contour-preamble (format nil "~%")
2296 preamble-in-arguments)))
2297 (if preamble-in-plot-options
2298 (setf preamble
2299 ($sconcat preamble-in-plot-options (format nil "~%")
2300 contour-preamble))
2301 (setq preamble contour-preamble)))
2302 (apply #'$plot3d
2303 (append (list expr)
2304 optional-args
2305 (list '((mlist) $palette nil))
2306 (list `((mlist) $gnuplot_preamble ,preamble))))))
2308 ;; plot3d
2310 ;; Examples:
2311 ;; plot3d (2^(-u^2 + v^2), [u, -3, 3], [v, -2, 2], [palette, false]);
2313 ;; plot3d ( log ( x^2*y^2 ), [x, -2, 2], [y, -2, 2], [grid, 29, 29]);
2315 ;; expr_1: cos(y)*(10.0+6*cos(x))$
2316 ;; expr_2: sin(y)*(10.0+6*cos(x))$
2317 ;; expr_3: -6*sin(x)$
2318 ;; plot3d ([expr_1, expr_2, expr_3], [x, 0, 2*%pi], [y, 0, 2*%pi],
2319 ;; ['grid, 40, 40], [z,-8,8]);
2321 ;; plot3d (cos (-x^2 + y^3/4), [x, -4, 4], [y, -4, 4],
2322 ;; [mesh_lines_color, false], [elevation, 0], [azimuth, 0], [colorbox, true],
2323 ;; [grid, 150, 150])$
2325 ;; spherical: make_transform ([th, phi,r], r*sin(phi)*cos(th),
2326 ;; r*sin(phi)*sin(th), r*cos(phi))$
2327 ;; plot3d ( 5, [th, 0, 2*%pi], [phi, 0, %pi], [transform_xy, spherical],
2328 ;; [palette, [value, 0.65, 0.7, 0.1, 0.9]], [plot_format,xmaxima]);
2330 ;; V: 1 / sqrt ( (x+1)^2+y^2 ) - 1 / sqrt( (x-1)^2+y^2 )$
2331 ;; plot3d ( V, [x, -2, 2], [y, -2, 2], [z, -4, 4])$
2334 (defun $plot3d
2335 (fun &rest extra-options
2336 &aux
2337 lvars trans xrange yrange
2338 functions exprn domain tem (options (copy-tree *plot-options*))
2339 ($in_netmath $in_netmath)
2340 (*plot-realpart* *plot-realpart*)
2341 gnuplot-term gnuplot-out-file file titles output-file
2342 (usage (intl:gettext
2343 "plot3d: Usage.
2344 To plot a single function f of 2 variables v1 and v2:
2345 plot3d (f, [v1, min, max], [v2, min, max], options)
2346 A parametric representation of a surface with parameters v1 and v2:
2347 plot3d ([f1, f2, f3], [v1, min, max], [v2, min, max], options)
2348 Several functions depending on the two variables v1 and v2:
2349 plot3d ([f1, f2, ..., fn, [v1, min, max], [v2, min, max]], options)")))
2351 (setf (getf options :type) "plot3d")
2353 ;; Ensure that fun is a list of expressions and maxima lists, followed
2354 ;; by a domain definition
2355 (if ($listp fun)
2356 (if (= 1 (length (check-list-plot3d fun)))
2357 ;; fun consisted of a single parametric expression
2358 (setq fun `(,fun ,(pop extra-options) ,(pop extra-options)))
2359 ;; fun was a maxima list with several independent surfaces
2360 (pop fun))
2361 ;; fun consisted of a single expression
2362 (setq fun `(,fun ,(pop extra-options) ,(pop extra-options))))
2364 ;; go through all the independent surfaces creating the functions stack
2365 (loop
2366 (setq exprn (pop fun))
2367 (if ($listp exprn)
2368 (progn
2369 (setq domain (check-list-plot3d exprn))
2370 (case (length domain)
2372 ;; exprn is a parametric representation of a surface
2373 (let (vars1 vars2 vars3)
2374 ;; list fun should have two valid ranges after exprn
2375 (setq xrange (check-range (pop fun)))
2376 (setq yrange (check-range (pop fun)))
2377 ;; list of the two variables for the parametric equations
2378 (setq lvars `((mlist),(second xrange) ,(second yrange)))
2379 ;; make sure that the 3 parametric equations depend only
2380 ;; on the two variables in lvars
2381 (setq vars1
2382 ($listofvars (mfuncall
2383 (coerce-float-fun (second exprn) lvars)
2384 (second lvars) (third lvars))))
2385 (setq vars2
2386 ($listofvars (mfuncall
2387 (coerce-float-fun (third exprn) lvars)
2388 (second lvars) (third lvars))))
2389 (setq vars3
2390 ($listofvars (mfuncall
2391 (coerce-float-fun (fourth exprn) lvars)
2392 (second lvars) (third lvars))))
2393 (setq lvars ($listofvars `((mlist) ,vars1 ,vars2 ,vars3)))
2394 (if (<= ($length lvars) 2)
2395 ;; we do have a valid parametric set. Push it into
2396 ;; the functions stack, along with their domain
2397 (progn
2398 (push `(,exprn ,xrange ,yrange) functions)
2399 ;; add a title to the titles stack
2400 (push "Parametric function" titles)
2401 ;; unknown variables in the parametric equations
2402 ;; ----- GNUPLOT 4.0 WORK-AROUND -----
2403 (when (and ($constantp (fourth exprn))
2404 (getf options :gnuplot_4_0))
2405 (setf (getf options :const_expr)
2406 ($float (meval (fourth exprn))))))
2407 (merror
2408 (intl:gettext "plot3d: there must be at most two variables; found: ~M")
2409 lvars))))
2412 ;; expr is a simple function with its own domain. Push the
2413 ;; function and its domain into the functions stack
2414 (setq xrange (second domain))
2415 (setq yrange (third domain))
2416 (push `(,(second exprn) ,xrange ,yrange) functions)
2417 ;; push a title for this plot into the titles stack
2418 (if (< (length (ensure-string (second exprn))) 36)
2419 (push (ensure-string (second exprn)) titles)
2420 (push "Function" titles)))
2423 ;; syntax error. exprn does not have the expected form
2424 (merror
2425 (intl:gettext "plot3d: argument must be a list of three expressions; found: ~M")
2426 exprn))))
2427 (progn
2428 ;; exprn is a simple function, defined in the global domain.
2429 (if (and (getf options :xvar) (getf options :yvar))
2430 ;; the global domain has already been defined; use it.
2431 (progn
2432 (setq xrange `((mlist) ,(getf options :xvar)
2433 ,(first (getf options :x))
2434 ,(second (getf options :x))))
2435 (setq yrange `((mlist) ,(getf options :yvar)
2436 ,(first (getf options :y))
2437 ,(second (getf options :y)))))
2438 ;; the global domain should be defined by the last two lists
2439 ;; in fun. Extract it and check whether it is valid.
2440 (progn
2441 (setq
2442 domain
2443 (check-list-plot3d (append `((mlist) ,exprn) (last fun 2))))
2444 (setq fun (butlast fun 2))
2445 (if (= 3 (length domain))
2446 ;; domain is valid. Make it the global one.
2447 (progn
2448 (setq xrange (second domain))
2449 (setq yrange (third domain))
2450 (setf (getf options :xvar) (second xrange))
2451 (setf (getf options :x) (cddr xrange))
2452 (setf (getf options :yvar) (second yrange))
2453 (setf (getf options :y) (cddr yrange)))
2454 (merror usage))))
2455 ;; ----- GNUPLOT 4.0 WORK-AROUND -----
2456 (when (and ($constantp exprn) (getf options :$gnuplot_4_0))
2457 (setf (getf options :const_expr) ($float (meval exprn))))
2458 ;; push the function and its domain into the functions stack
2459 (push `(,exprn ,xrange ,yrange) functions)
2460 ;; push a title for this plot into the titles stack
2461 (if (< (length (ensure-string exprn)) 36)
2462 (push (ensure-string exprn) titles)
2463 (push "Function" titles))))
2464 (when (= 0 (length fun)) (return)))
2466 ;; recover the original ordering for the functions and titles stacks
2467 (setq functions (reverse functions))
2468 (setq titles (reverse titles))
2470 ;; parse the options given to plot3d
2471 (setq options (plot-options-parser extra-options options))
2472 (setq tem (getf options :transform_xy))
2473 (setq *plot-realpart* (getf options :plot_realpart))
2475 ;; set up the labels for the axes, unless no box is being shown
2476 (unless (and (member :box options) (not (getf options :box)))
2477 (if (and (getf options :xvar) (getf options :yvar) (null tem))
2478 (progn
2479 ;; Don't set xlabel (ylabel) if the user specified one.
2480 (unless (getf options :xlabel)
2481 (setf (getf options :xlabel) (ensure-string (getf options :xvar))))
2482 (unless (getf options :ylabel)
2483 (setf (getf options :ylabel) (ensure-string (getf options :yvar)))))
2484 (progn
2485 (setf (getf options :xlabel) "x")
2486 (setf (getf options :ylabel) "y")))
2487 (unless (getf options :zlabel) (setf (getf options :zlabel) "z")))
2489 ;; x and y should not be bound, when an xy transformation function is used
2490 (when tem (remf options :x) (remf options :y))
2492 ;; Name of the gnuplot commands file and output file
2493 (setf gnuplot-term (getf options :gnuplot_term))
2494 (setf gnuplot-out-file (getf options :gnuplot_out_file))
2495 (if (and (find (getf options :plot_format) '($gnuplot_pipes $gnuplot))
2496 (eq gnuplot-term '$default) gnuplot-out-file)
2497 (setq file (plot-file-path gnuplot-out-file))
2498 (setq file
2499 (plot-file-path
2500 (format nil "maxout.~(~a~)"
2501 (ensure-string (getf options :plot_format))))))
2503 (and $in_netmath
2504 (setq $in_netmath (eq (getf options :plot_format) '$xmaxima)))
2506 ;; Set up the output file stream
2507 (let (($pstream
2508 (cond ($in_netmath *standard-output*)
2509 (t (open file :direction :output :if-exists :supersede))))
2510 (palette (getf options :palette))
2511 (legend (getf options :legend)) (n (length functions)))
2512 ;; titles will be a list. The titles given in the legend option
2513 ;; will have priority over the titles generated by plot3d.
2514 ;; no legend if option [legend,false]
2515 (unless (listp legend) (setq legend (list legend)))
2516 (when (member :legend options) ;; use titles given by legend option
2517 (if (first legend) (setq titles legend)) (setq titles nil))
2519 (unwind-protect
2520 (case (getf options :plot_format)
2521 ($gnuplot
2522 (setq output-file (gnuplot-print-header $pstream options))
2523 (when (and (member :gnuplot_pm3d options)
2524 (not (getf options :gnuplot_pm3d)))
2525 (setq palette nil))
2526 (format
2527 $pstream "~a"
2528 (gnuplot-plot3d-command "-" palette
2529 (getf options :gnuplot_curve_styles)
2530 (getf options :color)
2531 titles n)))
2532 ($gnuplot_pipes
2533 (when (and (member :gnuplot_pm3d options)
2534 (not (getf options :gnuplot_pm3d)))
2535 (setq palette nil))
2536 (check-gnuplot-process)
2537 ($gnuplot_reset)
2538 (setq output-file (gnuplot-print-header *gnuplot-stream* options))
2539 (setq
2540 *gnuplot-command*
2541 (gnuplot-plot3d-command file palette
2542 (getf options :gnuplot_curve_styles)
2543 (getf options :color)
2544 titles n)))
2545 ($xmaxima
2546 (when (getf options :ps_file)
2547 (setq output-file (list (getf options :ps_file))))
2548 (xmaxima-print-header $pstream options))
2549 ($geomview
2550 (format $pstream "LIST~%")))
2552 ;; generate the mesh points for each surface in the functions stack
2553 (let ((i 0))
2554 (dolist (f functions)
2555 (setq i (+ 1 i))
2556 (setq fun (first f))
2557 (setq xrange (second f))
2558 (setq yrange (third f))
2559 (if ($listp fun)
2560 (progn
2561 (setq trans
2562 ($make_transform `((mlist) ,(second xrange)
2563 ,(second yrange) $z)
2564 (second fun) (third fun) (fourth fun)))
2565 (setq fun '$zero_fun))
2566 (progn
2567 (setq lvars `((mlist) ,(second xrange) ,(second yrange)))
2568 (setq fun (coerce-float-fun fun lvars))
2569 (when (cdr
2570 ($delete
2571 (second lvars)
2572 ($delete
2573 (third lvars)
2574 ($listofvars (mfuncall fun (second lvars) (third lvars))))))
2575 (mtell (intl:gettext "plot3d: expected <expr. of v1 and v2>, [v1, min, max], [v2, min, max]~%"))
2576 (mtell (intl:gettext "plot3d: keep going and hope for the best.~%")))))
2577 (let* ((pl
2578 (draw3d
2579 fun (third xrange) (fourth xrange) (third yrange)
2580 (fourth yrange) (first (getf options :grid))
2581 (second (getf options :grid))))
2582 (ar (polygon-pts pl))
2583 (colors (getf options :color))
2584 (palettes (getf options :palette)))
2585 (declare (type (cl:array t) ar))
2587 (if trans (mfuncall trans ar))
2588 (if tem (mfuncall tem ar))
2590 (case (getf options :plot_format)
2591 ($gnuplot
2592 (when (> i 1) (format $pstream "e~%"))
2593 (output-points pl (first (getf options :grid))))
2594 ($gnuplot_pipes
2595 (when (> i 1) (format $pstream "~%~%"))
2596 (output-points pl (first (getf options :grid))))
2597 ($mgnuplot
2598 (when (> i 1) (format $pstream "~%~%# \"Fun~a\"~%" i))
2599 (output-points pl (first (getf options :grid))))
2600 ($xmaxima
2601 (if palettes
2602 (format $pstream " ~a~%" (xmaxima-palettes palettes i))
2603 (format $pstream " {mesh_lines ~a}"
2604 (xmaxima-color colors i)))
2605 (output-points-tcl $pstream pl (first (getf options :grid))))
2606 ($geomview
2607 (format $pstream "{ appearance { +smooth }~%MESH ~a ~a ~%"
2608 (+ 1 (first (getf options :grid)))
2609 (+ 1 (second (getf options :grid))))
2610 (output-points pl nil)
2611 (format $pstream "}~%"))))))
2613 ;; close the stream and plot..
2614 (cond ($in_netmath (format $pstream "}~%") (return-from $plot3d ""))
2615 ((eql (getf options :plot_format) '$xmaxima)
2616 (format $pstream "}~%")
2617 (close $pstream))
2618 (t (close $pstream)
2619 (setq $pstream nil))))
2620 (if (eql (getf options :plot_format) '$gnuplot)
2621 (gnuplot-process options file output-file)
2622 (cond ((getf options :run_viewer)
2623 (case (getf options :plot_format)
2624 ($xmaxima
2625 ($system
2626 (concatenate
2627 'string *maxima-prefix*
2628 (if (string= *autoconf-win32* "true") "\\bin\\" "/bin/")
2629 $xmaxima_plot_command)
2630 #-(or (and sbcl win32) (and ccl windows))
2631 (format nil " ~s &" file)
2632 #+(or (and sbcl win32) (and ccl windows))
2633 file))
2634 ($geomview
2635 ($system $geomview_command
2636 (format nil " \"~a\"" file)))
2637 ($gnuplot_pipes
2638 (send-gnuplot-command *gnuplot-command*))
2639 ($mgnuplot
2640 ($system
2641 (concatenate
2642 'string *maxima-plotdir* "/" $mgnuplot_command)
2643 (format nil " -parametric3d \"~a\"" file))))))))
2644 (cons '(mlist) (cons file output-file)))
2646 ;; Given a Maxima list with 3 elements, checks whether it represents a function
2647 ;; defined in a 2-dimensional domain or a parametric representation of a
2648 ;; 3-dimensional surface, depending on two parameters.
2649 ;; The return value will be a Maxima list if the test is succesfull or nil
2650 ;; otherwise.
2651 ;; In the case of a function and a 2D domain, it returns the domain, validated.
2652 ;; When it is a parametric representation it returns an empty Maxima list.
2654 (defun check-list-plot3d (lis)
2655 (let (xrange yrange)
2656 ;; Makes sure list has the form ((mlist) $atom item1 item2)
2657 (unless (and ($listp lis) (= 3 ($length lis)) (not ($listp (second lis))))
2658 (return-from check-list-plot3d nil))
2659 ;; we might have a function with domain or a parametric representation
2660 (if ($listp (third lis))
2661 ;; lis is probably a function with a valid domain
2662 (if ($listp (fourth lis))
2663 ;; we do have a function and a domain. Return the domain
2664 (progn
2665 (setq xrange (check-range (third lis)))
2666 (setq yrange (check-range (fourth lis)))
2667 (return-from check-list-plot3d `((mlist) ,xrange ,yrange)))
2668 ;; wrong syntax: [expr1, list, expr2]
2669 (return-from check-list-plot3d nil))
2670 ;; lis is probably a parametric representation
2671 (if ($listp (fourth lis))
2672 ;; wrong syntax: [expr1, expr2, list]
2673 (return-from check-list-plot3d nil)
2674 ;; we do have a parametric representation. Return an empty list
2675 (return-from check-list-plot3d '((mlist)))))))