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