Simplifying functions: exp and sqrt



I have studied in more detail the simple functions sqrt and exp.

I think, this can be the code, which will help to get the most simple
and consistent implementation:

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Implementation of the Square root function

(defprop $sqrt %sqrt verb)
(defprop $sqrt %sqrt alias)

(defprop %sqrt $sqrt noun)
(defprop %sqrt $sqrt reversealias)

(defprop %sqrt simpsqrt operators)

(defun $sqrt (z)
  (simplify (list '(%sqrt) z)))

(defun simpsqrt (x y z)
  (declare (ignore y))
  (oneargcheck x)
  (simplifya (list '(mexpt) (cadr x) '((rat simp) 1 2)) z))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Implementation of the Exp function.

(defprop $exp %exp verb)
(defprop $exp %exp alias)

(defprop %exp $exp noun)
(defprop %exp $exp reversealias)

(defprop %exp simpexp operators)

(defun $exp (z)
  (simplify (list '(%exp) z)))

;; We support an unsimplified noun form.
;; There is some code, which depends on it.
(defun $exp-form (z)
  (list '(mexpt) '$%e z))

(defun simpexp (x y z)
  (declare (ignore y))
  (oneargcheck x)
  (simplifya (list '(mexpt) '$%e (cadr x)) z))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

What is changed:

1. Square root function:
   a) The verb-function $sqrt returns a simplified result.
   b) The numerical evaluation has been moved to simpexpt.
      This is the code in simpexpt:
      ;; Check numerical evaluation for sqrt
      ((and (alike1 pot '((rat) 1 2))
            (or (setq res (flonum-eval '%sqrt gr))
                (and (not (member 'simp (car x) :test #'eq))
                     (setq res (big-float-eval '%sqrt gr)))))
      (return res))

2. Exponential function:
   a) The verb function $exp returns a simplified result.
   b) The properties noun, verb, alias, and reversealias
      are fully implemented.
   c) To be consistent with old code a function $exp-form
      is supported, which returns an unsimplified noun form.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Some examples:

sqrt(z) and z^(1/2) evaluate both numerically for complex numbers:

(%i4) sqrt(1.0+%i);
(%o4) .4550898605622273*%i+1.09868411346781
(%i5) (1.0+%i)^(1/2);
(%o5) .4550898605622273*%i+1.09868411346781

Mapping of sqrt over complex numbers now works as expected:

(%i7) map(sqrt,[4.0,1.0*%i,1.0+%i]);
(%o7) [2.0,.7071067811865475*%i+.7071067811865476,
       .4550898605622273*%i+1.09868411346781]

The noun form of exp simplifies as expected: 

(%i8) exp(1);
(%o8) %e
(%i9) 'exp(1);
(%o9) %e

Maxima error, not Lisp error, when wrong number of arguments:

(%i1) exp(1,2);
Wrong number of arguments to exp
 -- an error.  To debug this try debugmode(true);

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

I had a look at the implemented Lisp properties. The following
properties are present for the sqrt function:

   $SQRT VERB         %SQRT
         ALIAS        %SQRT

   %SQRT NOUN         $SQRT
         REVERSEALIAS $SQRT
         OPERATORS    SIMPSQRT

I think this is the minimum for a consistent implementation of a fully
simplifying mathematical function. Noun, verb, alias, and reversealias
are present. A simplifying function simpsqrt is put on the property
list.

The user input never calls the verb function. The verb function can be
used as a shortcut to call the simplifier within Lisp code. The
following calls to the simplifier are equivalent:

(simplifya (list '(%sqrt) 4.0) nil)
(simplify (list '(%sqrt) 4.0))
(take '(%sqrt) 4.0)
($sqrt 4.0)

This is what we have now for the exp function:

   $EXP OPERATORS    SIMPEXP

   %EXP  no related entry

And these are some problems:

   1. Exp function is not a simplifying function.
   2. The simplifying function simpexp is only called
      by a direct call within Lisp code.
   3. User input calls the verb function. That is
      the reason for Lisp errors.
   4. Unsimplified expressions like 'exp(1).
   5. Noun/verb problems because the noun %exp is not
      known to Maxima, e.g. no further simplifications.

The suggested implementation will give the following entries:

   $EXP VERB         %EXP
        ALIAS        %EXP

   %EXP NOUN         $EXP
        REVERSEALIAS $EXP
        OPERATORS    SIMPEXP

With these entries the exp function will work as expected.

A remark on the efficiency of different implementations:

I have traced different implementations of mathematical functions. At
first it seems to be equivalent to have a simplifying exp function like
the suggested implementation or the implementation we have.

My observation is that a fully implemented simplifying function always
need the fewest calls to the simplifier. Every time a verb function is
called by user input or unsimplified noun forms are generated, extra
calls to the simplifier are needed and often be done by meval
automatically. The testsuite calls the simplifier about 10^7 times.
There might be a lot of calls, which are not necessary, when we
implement the functions more consistent.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

The testsuite and existing code:

The testsuite and the share-testsuite have no problems with the changes
to the sqrt function.

The verb function $exp, which returns now an unsimplified noun form, is
called at about 25 places. In most cases an extra call to the simplifier
is already done. These calls can be removed. There are a few places,
where the code depends on an unsimplified noun form. Here the function
$exp-form can be used.

With this changes we have no problems with the testsuite and the
share-testsuite.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Remark: 

I have worked on this simple functions to get examples how to get a more
consistent behavior and simplification for other mathematical functions
too.

We have a lot of different ways to implement a mathematical function in
Maxima. Most of the implementations seems to behave equivalent, but
sometimes we have a subtle different behavior. Some points are: missing
properties, unnecessary calls to resimplify, return of unsimplified
results, bypassing the simplifier, bypassing eqtest, put in simp flags
by hand, ...

Two examples for small differences:

The function jacobi_sn has not the property alias for the symbol
$jacobi_sn. The user always calls the verb function. Therefore we get
Lisp Errors when we have a wrong number of arguments. But both,
jacobi_sn and the noun 'jacobi_sn simplifies accordingly, because the
property reversealias is present. jacobi_sn needs 8 calls to the
simplifier to simplify an expression with atomic arguments. A function
with two atomic arguments and which has all properties present needs 5
calls to the simplifier.

The function beta is implemented only with the symbol $beta. So beta
does not follow the convention to use the noun symbol %beta for a
simplifying function. The noun %beta is not known to Maxima. Beta seems
to work like other functions too. There are no extra calls to the
simplifier. The only difference seems to be, that the noun form does not
simplify.

Dieter Kaiser