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