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