As the author of the one of the patches, possibly soon to be obsolete, I like this proposal.
The one thing I don't like is the name. I prefer nouns, as in A.derivative(). A.diff() is short for A.differentiate(), which sounds like an in place procedure to me. Standardizing on an abbreviation is madness! My preferred examples are characteristic polynomials and coefficients of polynomials. If I want a characteristic polynomial, I look for that, not 'charpoly'. Also, in many places A.coefficients() or A.coefficient_list() is abbreviated A.coeffs(). It makes it harder to search. If I know what I want (a derivative!) that's what I will look for. Not a diff. Nick PS. IIRC, the patch in trac would optimize only the case derivative (x, 3). On 24-Feb-08, at 6:37 AM, David Harvey wrote: > > 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 -~----------~----~----~----~------~----~------~--~---