Simplifying functions in Maxima code



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)))))