Opened 5 years ago

# derivative of integer wrt to variable in polynomial ring should belong to that ring, not symbolic ring

Reported by: Owned by: dgulotta major sage-duplicate/invalid/wontfix calculus N/A u/dgulotta/derivative_of_integer_wrt_to_variable_in_polynomial_ring_should_belong_to_that_ring__not_symbolic_ring 0dc171fcd64c77aa3e80e958312f42b4f11c31ee

### Description

If I try to take the derivative of an integer (or nonzero rational, or integer mod n), then the result is an element of the symbolic ring:

```sage: R.<x>=ZZ[]
sage: derivative(0,x).parent()
Symbolic Ring
```

It seems like it would be more natural for the returned value to belong to the ring containing x instead.

This may seem kind of pedantic, but it did trip me up when I was working with a list of polynomials, some of which were constant, and things were getting cast to Expression unexpectedly.

I am not particularly familiar with the Sage codebase, but I am attaching a patch that seems to fix the issue.

### comment:1 Changed 4 years ago by chapoton

If you take care to use the correct zero, this just works:

```sage: R.zero().derivative(x).parent()
Univariate Polynomial Ring in x over Integer Ring
```

But there is room for improvement, for sure.

### comment:2 Changed 3 months ago by charpent

• Milestone changed from sage-7.3 to sage-duplicate/invalid/wontfix
• Status changed from new to needs_review

The derivative of a symbolic expression is a symbolic expression :

```sage: R1.<t>=ZZ[]
sage: derivative(x^2+x+1,t).parent()
Symbolic Ring
```

Therefore this :

```sage: derivative(0,t).parent()
Symbolic Ring
```

is a special case, indicating that 0 is cast to a symbolic expression (probably by `diff`...).

However :

```sage: derivative(R1(0),t).parent()
Univariate Polynomial Ring in t over Integer Ring
```

conforms to the requirement that the derivative of an object belongs its parent ring.

Pedantism works both ways...

==> marking as invalid and requesting review in order to close.

### comment:3 follow-up: ↓ 4 Changed 3 months ago by dgulotta

This behavior is confusing. I think it's reasonable to expect that the derivative(f,g) will lie in the smallest ring containing f and g. Why not fix this?

### comment:4 in reply to: ↑ 3 Changed 3 months ago by charpent

This behavior is confusing. I think it's reasonable to expect that the derivative(f,g) will lie in the smallest ring containing f and g. Why not fix this?

In order

• to avoid introducing a lot of special-casing...
• to keep (at least an appearance of) reason : the differential of a "constant" does not make sense, whereas the differential of a function, expression or polynomial being respectively a function, expression or polynomial does...

...even when this function, expression or polynomial happens to be a "constant" or degree-0 monomial, in which case the derivative can be taken to be the null "constant" or degree-0 monomial.

Your remark may be more relevant in the reverse case:

```sage: R1.<t>=QQbar[]
sage: foo=t^2
sage: integral(foo,t).parent()
Univariate Polynomial Ring in t over Algebraic Field
```

So far, so good. But

```sage: integral(0,t).parent()
Symbolic Ring
```

is nonsensical, unless we mean to do implicitly :

```sage: integral(R1(0),t).parent()
Univariate Polynomial Ring in t over Algebraic Field
```

In other words, take note that

```sage: R1(0) is 0
False
```

even if

```sage: R1(0).is_zero()
True
```

HTH,

### comment:5 follow-up: ↓ 6 Changed 3 months ago by dgulotta

It is difficult to do the right thing in all cases but I think that the patch that I submitted improves the situation for derivatives. I could write something similar for integrals if there is agreement that this would be useful.

The reason why I don't like casting things into the symbolic ring is that it leads to errors that are very difficult to track down. For example:

```sage: R.<x>=QQ[]
sage: l = [1,x,x*(x-1),x*(x-1)*(x-2)]
sage: [derivative(f,x).monomial_coefficient(x) for f in l]
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-16-ff4d7a390725> in <module>
----> 1 [derivative(f,x).monomial_coefficient(x) for f in l]

<ipython-input-16-ff4d7a390725> in <listcomp>(.0)
----> 1 [derivative(f,x).monomial_coefficient(x) for f in l]

/ext/sage/sage-9.2/local/lib/python3.8/site-packages/sage/structure/element.pyx in sage.structure.element.Element.__getattr__ (build/cythonized/sage/structure/element.c:4703)()
491             AttributeError: 'LeftZeroSemigroup_with_category.element_class' object has no attribute 'blah_blah'
492         """
--> 493         return self.getattr_from_category(name)
494
495     cdef getattr_from_category(self, name):
/ext/sage/sage-9.2/local/lib/python3.8/site-packages/sage/structure/element.pyx in sage.structure.element.Element.getattr_from_category (build/cythonized/sage/structure/element.c:4815)()
504         else:
505             cls = P._abstract_element_class
--> 506         return getattr_from_other_class(self, cls, name)
507
508     def __dir__(self):
/ext/sage/sage-9.2/local/lib/python3.8/site-packages/sage/cpython/getattr.pyx in sage.cpython.getattr.getattr_from_other_class (build/cythonized/sage/cpython/getattr.c:2620)()
370         dummy_error_message.cls = type(self)
371         dummy_error_message.name = name
--> 372         raise AttributeError(dummy_error_message)
373     attribute = <object>attr
374     # Check for a descriptor (__get__ in Python)
AttributeError: 'sage.symbolic.expression.Expression' object has no attribute 'monomial_coefficient'
```

It is not at all clear from the error message that `1` needs to be replaced with `R(1)`. And this is just a toy example; in real code the failure could occur much later down the line. So I think it is bad for a function like `derivative` that sometimes returns polynomials to implicitly cast things into the symbolic ring when none of the arguments live in the symbolic ring. The patch that I submitted should significantly reduce these types of errors.

In principle I think it is better to raise an error with a detailed message than to implicitly cast into the symbolic ring. I guess it may be too late to make that change since it might break existing code. Attempting to cast into a polynomial ring when possible seems like a reasonable compromise.

### comment:6 in reply to: ↑ 5 Changed 3 months ago by charpent

[ Snip... ]

The patch that I submitted should significantly reduce these types of errors.

Which patch ? I find no patch in the ticket.

More generally, I think that the problem is to define and compute "the smallest ring containing f and g".

To illustrate :

```sage: R1.<t>=QQ[]
sage: t.derivative(t)
1
sage: t.derivative(t).parent()
Univariate Polynomial Ring in t over Rational Field
sage: 1.derivative(t).parent()
## [ Snip... ]
AttributeError: 'sage.rings.integer.Integer' object has no attribute 'derivative'
sage: t.parent()(1).derivative(t).parent()
Univariate Polynomial Ring in t over Rational Field
```

This cast is reasonable and might be expected (i. e. one can reasonably expect `1.differential(t)` to return `R1`'s `0`.

Harder :

```sage: R2.<u>=QQ[]
sage: t.derivative(u)
## [ Snip... ]
ValueError: cannot differentiate with respect to u
```

One might expect the `0` with :

• this zero belonging to `PolynomialRing(QQ,"v1,v2")`, and
• some "automagic glue" realizing `v1==t, v2==u`.

I'm not sure that this can be expressed in Sage...

For the integrals :

```sage: 1.integral(t)
## [ Snip]
AttributeError: 'sage.rings.integer.Integer' object has no attribute 'integral'
sage: t.parent()(1).integral(t)
t
```

Again, a "reasonable" cast.

The case `t.integral(u)` leads to the same conclusion as for `t.differentiate(u)`.

And we might have worse difficulties : what should be the "smallest ring" containing `Zmod(3)['v']` and `QQbar['w']` ? Ditto for ring of matrices...

Casting to SR is, indeed, far from ideal, but it seems tome that the possible enhancements are fraught with more difficulties than they solve.

### comment:7 follow-up: ↓ 8 Changed 3 months ago by dgulotta

There is an attachment to this ticket, which is a patch. The patch uses `sage.structure.element.get_coercion_model`. I don't claim to be an expert on this function but it seems to do the right thing in cases that would come up in practice.

### comment:8 in reply to: ↑ 7 Changed 3 months ago by charpent

There is an attachment to this ticket, which is a patch. The patch uses `sage.structure.element.get_coercion_model`. I don't claim to be an expert on this function but it seems to do the right thing in cases that would come up in practice.

Would you mind submitting a branch, as described in Sagemath's developer's guide ?

### comment:9 Changed 3 months ago by dgulotta

• Branch set to u/dgulotta/derivative_of_integer_wrt_to_variable_in_polynomial_ring_should_belong_to_that_ring__not_symbolic_ring

### comment:10 Changed 3 months ago by vdelecroix

• Commit set to 0dc171fcd64c77aa3e80e958312f42b4f11c31ee

It is a bad idea to put a lot of code inside the `try` block. Only keep there the minimal amount of code that could potentially raise an error. For example neither `elts.append(f)` nor `cm = get_coercion_model()` should be there.

New commits:

 ​0dc171f `derivative: try to find a common parent before casting to SR`

### comment:11 Changed 3 months ago by tscrim

This might be an interesting data point:

```sage: R.<x> = ZZ[]
sage: S.<y> = ZZ[]
sage: derivative(S.zero(), x).parent()
Univariate Polynomial Ring in y over Integer Ring
```
Note: See TracTickets for help on using tickets.