Until now, there have been two alternatives if you wanted to add a new
function with simplifying rules to Maxima: you could either work with the
pattern-matching system (tellsimp etc.) or you could write a simplifier in
Lisp code.
I have now written a little experimental package which allows you to write
simplifiers in Maxima code. For example, here is some basic code for a
simplifying absolute value function called aabs.
/* Define simplifier */
simp_aabs(x):=
block([inflag:true], /* Work on internal form */
if maybe(x>=0)=true then x /* Must use maybe to avoid errors */
elseif maybe(x<=0)=true then -x
elseif atom(x) then simpfuncall('aabs,x)
/* simpfuncall(f,x) constructs f(x) and
marks it as already simplified. */
elseif part(x,0)="*" then map(simp_aabs,x)
else simpfuncall('aabs,x));
simplifying(aabs,simp_aabs)$
aabs(x) => aabs(x)
aabs(-x/2) => aabs(x)/2
aabs(x^2) => x^2
So far, it is just like an evaluating function. But here is where the
difference begins.
ex: aabs(x) => aabs(x)
subst(x^2,x,ex) => x^2 /* Subst resimplifies */
Try it out and give me feedback -- I plan to put it in the contrib section
of share when it's a bit more stable.
-s
----------------------------------------
This package, simplifying.lisp, allows the definition of simplifying
functions using the Maxima language (not Lisp). After simplifying(f,simp_f)
is called, simp_f is called every time an expression in f(...) needs to be
simplified or resimplified. simp_f can construct a simplified expression
f(...) using simpfuncall('f,...) or simpfunmake('f,...).
Nothing prevents the user from overriding built-in functions (with possibly
dire consequences) or from invoking simplification recursively (which is
sometimes a perfectly sensible thing to do, other times a bug. I considered
checking for these cases, but they may sometimes be useful.
;;; Package to allow Maxima-level user-defined simplifying functions
;;;
;;; For example, suppose we want to write a step function stepfn(x)
;;; which is 0 for x<- and 1 for x>0.
;;;
;;; /* Must guarantee it is not a simplifying function before redefining */
;;; simplifying('stepfn,false)$
;;; /* Define simplifying function */
;;; stepfn(x):=
;;; block([prederror:false],
;;; if is(x<=0)=true then 0
;;; elseif is(x>0)=true then 1
;;; else simpfuncall('stepfn,x))$
;;; /* Declare stepfn to be simplifying */
;;; simplifying('stepfn)$
;;;
;;; /* Test simple cases */
;;; stepfn(-x^2); /* 0 */
;;; stepfn(x^2+1); /* 1 */
;;; ex: stepfn(x^2); /* stepfn(x^2) -- no simplifications apply */
;;; assume(x>0)$
;;; ex; /* Assumptions not consulted */
;;; resimplify(ex):=expand(ex,0,0)$
;;; /* Force resimplification */
;;; resimplify(ex); /* 1 */
;;; forget(x>0)$
;;; resimplify(ex); /* stepfn(x^2) */
;;; Utilities
(defun defined-functionp (ex)
(cond ((null ex) nil)
((symbolp ex)
(if (or (fboundp ex)
(safe-mgetl ex '(mexpr mmacro)))
t))
((and (not (atom ex))
(eq (caar ex) 'lambda))
t)
(t nil)))
;;; Declare a user Maxima function to be a simplifying function
;;; simplifying(f,g) -- uses g as the simplifier
;;; simplifying(f,false) -- removes simplifying property
;;;
;;; You can override built-in simplifiers, but it is not recommended
(defun $simplifying (f simplifier)
(if (not (symbolp f)) (merror "Simplifying function ~s must be a symbol"
f))
(if (not (defined-functionp simplifier))
(mtell "Warning: simplifier function ~M is not defined"
simplifier))
(if (and (get f 'operators) (not (get f 'user-simplifying)))
(mtell "Warning: ~M is overriding the built-in simplifier for ~M"
simplifier f))
(cond ((null simplifier)
(setf (get f 'operators) nil)
(setf (get f 'user-simplifying) nil))
(t (setf (get f 'operators) #'user-simplifying)
(setf (get f 'user-simplifying) simplifier))))
;;; Create the expression fun(args...) and mark it as simplified.
;;; Thus, simpfuncall(sin,0) => sin(0), not 0, but resimplifying with
;;; expand(simpfuncall(sin,0)) does simplify to 0.
;;; It is generally not recommended to use this for functions with
;;; built-in simplifiers. (i.e. be very careful)
(defun $simpfuncall (fun &rest args)
(if (not (or (symbolp fun) ($subvarp fun)
(and (not (atom fun)) (eq (caar fun) 'lambda))))
(merror "Bad first argument to `simpfuncall': ~M" fun))
(simpcons (getopr fun) args))
(defun $simpfunmake (fun args)
(if (not ($listp args)) (merror "Bad second argument to `simpfunmake': ~M"
args))
($simpfuncall fun (cdr args)))
(defmfun simpcons (op args)
(if (symbolp op)
`((,op simp) , at args)
`((mqapply simp) ,op , at args)))
;;; The generic simplifying function for user simplification functions
(defun user-simplifying (l ignore simpflag)
(let* ((op (caar l))
(simplifier (get op 'user-simplifying))
;; args are (re)simplified *outside* the simplification fnc
(args (mapcar #'(lambda (i) (simpcheck i simpflag)) (cdr l))))
(let ( ;; args have already been resimplified if necessary
(dosimp nil))
(declare (special dosimp))
(if (defined-functionp simplifier)
(mapply simplifier args op)
(simpcons op args)))))