After hearing some ideas on IRC regarding the derivatives mess, in  
this email I propose a plan. It's rough around the edges. Comments  
welcome.


CURRENT SITUATION

There are currently at least 18 different functions for  
differentiation in Sage, attached to polynomials, power series,  
symbolics, and other things. (See my previous email for a complete  
list.)

There are about 4 "diff", 12 "derivative", one "differentiate", and  
sometimes (but not always) these names are aliases to each other.

I count six different call signatures (all "derivative"s replaced by  
"diff" in the following list):

def diff(self): mainly for univariate polynomial and power series types.

def diff(self, verbose=None): only in the sage.functions.elementary  
module. All doctests are switched off on this file. The documentation  
says that this function *integrates* things! The verbose flag seems  
to be something passed to maxima.

def diff(self, *args): only in calculus.calculus.SymbolicExpression.  
This is the most powerful version, and is the closest to what I want  
to eventually support everywhere.

def diff(f, *args, **kwds): only in calculus.functional. This is a  
global, and just dispatches to the diff method on f.

def diff(self, variable, have_ring=False): in the libsingular  
multivariate polynomial code, and also mysteriously crops up in  
polynomial_modn_dense_ntl.pyx. The parameter "have_ring" seems to be  
ignored everywhere, and somewhere claims that it is needed for  
"compatibility reasons". But I can't figure out what this means.

def diff(self, var='x', n=1): only in  
interfaces.maxima.MaximaElement. It means differentiate n times with  
respect to x. Note that the variable is supplied as a *string*. We  
might want to leave this one out of the discussion; I guess here the  
diff function is supposed to conform to maxima semantics (about which  
I know nothing) rather than fit into the sage object model.

There are three open tickets on this issue:

http://trac.sagemath.org/sage_trac/ticket/756 (patch is problematic)
http://trac.sagemath.org/sage_trac/ticket/753 (not much interesting  
here)
http://trac.sagemath.org/sage_trac/ticket/1578 (there's a patch here,  
might need to get superseded, sorry Nick)

There are two separate issues here: function naming, and function  
signatures. The former is much simpler, so I'll discuss that first.


FUNCTION NAMING

I propose that we deprecate "differentiate".

I propose that we use "diff" as the basic name, and have "derivative"  
as an alias for any object supporting "diff".

I propose that there be a lower-level function "_diff" with a simpler  
signature, see below.


FUNCTION SIGNATURES

Here is an approximation to what I would like to see.

Any object F supporting differentiation should have a function  
"_diff" (def or cpdef) taking at least one argument var=None. It can  
have additional arguments, but these must be optional. For example:
    def _diff(self, var=None)
    def _diff(self, var=None, have_ring=False)
    def _diff(self, var=None, verbose=False)

If var is supplied, it should be a variable object (for example, a  
symbolic variable, or a generator of a polynomial ring). It need not  
lie in the parent of F. Examples:

* If F is in S[x] for some ring S, and you call F._diff(x), you get  
what you expect.
* If F is in S[x] for some ring S, and you call F._diff(y), then  
G._diff(y) gets called for each coefficient of F.
* If F is in the symbolic ring, then var can be any symbolic variable.

If var is None, the object makes a decision about the default  
variable and uses that. For example:

* a univariate polynomial or power series will differentiate w.r.t.  
the generator.
* a symbolic expression containing precisely one variable will use  
that variable.
* a multivariate polynomial will raise an exception.
* a symbolic expression containing more than one variable will raise  
an exception.

Now we come to the "diff" method. It must have an "*args"-style  
argument, which must be interpreted according to the following list  
of examples (which is almost clear enough to serve as a  
specification :-)):

    F.diff(): equivalent to F._diff(None)
    F.diff(2): equivalent to F._diff(None)._diff(None)
    F.diff(x): equivalent to F._diff(x)
    F.diff(x, 3): equivalent to F._diff(x)._diff(x)._diff(x)
    F.diff(x, y): equivalent to F._diff(x)._diff(y)
    F.diff(x, 3, y, 2, z): equivalent to F._diff(x)._diff(x)._diff 
(x)._diff(y)._diff(y)._diff(z)
    F.diff(2, x): equivalent to F._diff(None)._diff(None)._diff(x)  
[this one currently causes an infinite loop for symbolic objects!]
    F.diff([x, y, z]): equivalent to F._diff(x)._diff(y)._diff(z)
    F.diff((x, y, z)): equivalent to F._diff(x)._diff(y)._diff(z)

For the list and tuple versions, it must be the only parameter, and  
repetition counts are not allowed.

When I say "equivalent" in the above descriptions, I don't mean it  
literally has to call _diff that way, I just mean the behaviour  
should be equivalent.

Discussion on IRC concluded that partial derivatives need not  
commute, so we always have to do the derivatives in the order  
specified. (Of course implementations are free to use commutativity  
if they know about it, but the general case shouldn't assume it.)

To avoid code duplication, there will be two global helper functions.

The first one is
    def diff_parse(args)
which takes a list/tuple like [2, x, 3, y] and converts it to a list/ 
tuple like [None, None, x, x, x, y].

The second one is
    def multi_diff(F, args):
       for arg in args:
          F = F._diff(arg)
       return F

Therefore it will always be possible to implement diff via just:
    def diff(self, *args):
       return multi_diff(self, diff_parse(args))
as long as _diff has been implemented (which it should have been!)

Of course you can put in a more efficient implementation if you want,  
for example for univariate polynomials it might be:
    def diff(self, *args):
       if not args:
          return self._diff()
       return multi_diff(self, diff_parse(args))

And of course a caller is always free to directly call _diff instead  
of diff, if they know exactly what they want.

NOTE: conspicuously absent is the **kwds parameter in the above spec.  
I don't know quite how to do this in an efficient way. I'm worried  
the overhead will kill us in the last example. Any thoughts about  
this are welcome.

david


--~--~---------~--~----~------------~-------~--~----~
To post to this group, send email to sage-devel@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/sage-devel
URLs: http://www.sagemath.org
-~----------~----~----~----~------~----~------~--~---

Reply via email to