Repository: sagemath/sage

Issue:
Document derivative operator and notations
<div id="comment:0"></div>

Taking the derivative of a symbolic function returns the D-notation:

```
sage: var('x y z')
sage: f(x) = function('f',x,y,z);
sage: f(x).diff(x,y)
D[0, 1](f)(x, y, z)
```

Unfortunately, the meaning of this notation is not documented anywhere, neither in `diff()`, nor in `derivative()` nor in `function()`. There is a ton of tickets about improving ambiguities and malfunctions related to this notation, but it would be very helpful to at least document how it is supposed to work and what it means if a user sees output as above.

See here for related tickets:
* #6344 - allow typesetting in "diff" format (possibly only as non-default option)
* #6756 - add *input* to Sage in "diff" format for derivatives (the most controversial)
* #6480 - clarify or fix substituting functions inside of symbolic derivatives
* #7401 - bug in our interaction with Maxima with evaluating derivative at a point (needs work due to multivariate derivatives not being there)
* #12796 - allow evaluation at points

and these discussions:

- [sage-devel, 2012-04, D notation input for ODEs](https://groups.google.com/g/sage-devel/c/xBHw11qUARg)
- [sage-devel, 2009-07, Is new symbolic derivative really worth the efforts?](https://groups.google.com/g/sage-devel/c/_xD5lymnTuo)


CC:  @kcrisman @eviatarbach @slel

Component: **symbolics**

_Issue created by migration from https://trac.sagemath.org/ticket/17445_




Discussion:
<div id="comment:1" align="right">comment:1</div>

OK, I do not know where it should go in the documentation for best visibility (I'd think in "diff" somewhere), but the explanation should be along the lines of:

Partial derivatives are represented in sage using differential operators, referencing the position of the variable with respect to which the partial derivative is taken. This means that for a function f in $r+1$ variables we have

\[D[i_1,\ldots,i_n](f)(x_0,\ldots,x_r) = \left.\frac{\partial f(t_0,\ldots,t_r)}{\partial t_{i_1}\cdots \partial t_{i_n}} \right|_{t_0=x_0,\ldots,t_r=x_r}\]

An advantage of this notation is that it is clear which derivative is taken, regardless of the names of the variables. For instance, if we have

```
sage: var("x,y,t")
sage: f(x,y)=function('f',x,y)
sage: g=f(x,y).diff(x,y); g
D[0, 1](f)(x, y)
sage: g.subs(x=1,y=1)
D[0, 1](f)(1, 1)
sage: g.subs(x=t,y=t+1)
D[0, 1](f)(t, t+1)
```
Note that in the last two lines are completely unambiguous using operator notation, whereas Leibnitz notation would require the use of some arbitrary explicit choice of auxiliary variable names.

Resolving patch:
diff --git a/src/sage/symbolic/all.py b/src/sage/symbolic/all.py
--- a/src/sage/symbolic/all.py
+++ b/src/sage/symbolic/all.py
@@ -18,3 +18,5 @@
 from .units import units
 
 π = pi
+
+from .operators import D
diff --git a/src/sage/symbolic/operators.py b/src/sage/symbolic/operators.py
--- a/src/sage/symbolic/operators.py
+++ b/src/sage/symbolic/operators.py
@@ -194,3 +194,65 @@ def parameter_set(self):
             [0, 1]
         """
         return self._parameter_set
+
+class DerivativeOperator():
+    """
+    Derivative operator.
+
+    Acting with this operator onto a function gives a new operator (of
+    type :class:`FDerivativeOperator`) representing the function
+    differentiated with respect to one or multiple of its arguments.
+
+    This operator takes a list of indices specifying the position of
+    the arguments to differentiate. For example, D[0, 0, 1] is an
+    operator that differentiates a function twice with respect to its
+    first argument and once with respect to its second argument.
+
+    EXAMPLES::
+
+        sage: x, y = var('x,y'); f = function('f')
+        sage: D[0](f)(x)
+        diff(f(x), x)
+        sage: D[0](f)(x, y)
+        diff(f(x, y), x)
+        sage: D[0, 1](f)(x, y)
+        diff(f(x, y), x, y)
+        sage: D[0, 1](f)(x, x^2)
+        D[0, 1](f)(x, x^2)
+
+    """
+    class DerivativeOperatorWithParameters():
+        def __init__(self, parameter_set):
+            self._parameter_set = parameter_set
+        def __call__(self, function):
+            return FDerivativeOperator(function, self._parameter_set)
+        def __repr__(self):
+            """
+            Return the string representation of this derivative operator.
+
+            EXAMPLES::
+
+                sage: D[0]
+                D[0]
+                sage: D[0, 1]
+                D[0, 1]
+            """
+            return "D[%s]" % (", ".join(map(repr, self._parameter_set)))
+
+    def __getitem__(self, args):
+        """
+        TESTS:
+
+        The order in which the indices are given should not matter::
+
+            sage: x, y = var('x,y'); f = function('f')
+            sage: bool(D[0, 1, 0](f)(x, y) == D[0, 0, 1](f)(x, y))
+            True
+            sage: bool(D[1, 0, 0](f)(x, y) == D[0, 0, 1](f)(x, y))
+            True
+        """
+        if not isinstance(args, tuple):
+            args = (args,)
+        return self.DerivativeOperatorWithParameters(args)
+
+D = DerivativeOperator()


Test patch: