sagemath / sage

Main repository of SageMath
https://www.sagemath.org
Other
1.45k stars 482 forks source link

Derivative of the symbolic sum of function of two variables #32161

Open b7a46a19-303e-4ecf-84fd-1c9426f587e4 opened 3 years ago

b7a46a19-303e-4ecf-84fd-1c9426f587e4 commented 3 years ago

Let's try to differentiate the symbolic sum of function of two variables.

sage: x,y = var("x,y")
sage: a,b = var("a,b")
sage: F = function("F")(x, y)
sage: from sage.calculus.calculus import symbolic_sum
sage: s = symbolic_sum(F(x, y), x, a, b)
sage: s.diff(y)
diff(F(x, y), y)*D[0](sum)(F(x, y), x, a, b)

But expected something like

sum((diff(F(x, y), y), x, a, b)

or


sum(D[1](F(x, y)), x, a, b)

CC: @nbruin @slel @mkoeppe @EmmanuelCharpentier

Component: symbolics

Author: Alexey Drozdov

Branch/Commit: u/gh-daju1/derivative_of_the_symbolic_sum_of_function_of_two_variables @ 6afad65

Issue created by migration from https://trac.sagemath.org/ticket/32161

b7a46a19-303e-4ecf-84fd-1c9426f587e4 commented 3 years ago
comment:1

Attachment: bug Derivative of the symbolic sum of function of two variables.ipynb.gz

nbruin commented 3 years ago
comment:2

Attachment: bug Derivative of the symbolic sum of function of two variables.2.ipynb.gz

Confirming the bug reported here:

sage: var("x,a,b")
(x, a, b)
sage: S=sum(f(x),x,a,b)
sage: S
sum(f(x), x, a, b)
sage: S.diff(x)
diff(f(x), x)*D[0](sum)(f(x), x, a, b) + D[1](sum)(f(x), x, a, b)

Clearly, sage has no knowledge on how to differentiate sums (it just considers the sum as an unknown function in four variables). The first step would be to throw an error, because things like 'sum(f(x+y),x,a,b+y).diff(y)' can probably not be handled.

In most cases, the most useful answer would probably be to differentiate term-wise (provided the differentiating variable does not occur in the bounds), which would be the correct answer for formal sums and absolutely convergent ones.

b7a46a19-303e-4ecf-84fd-1c9426f587e4 commented 3 years ago
comment:3

My investigation of this issue shown that giac, maxima and sympy solve this example in true way.

Let's try giac:

sage: sum_str = "sum(F(sageVARx,sageVARy), sageVARx, sageVARa, sageVARb)"                                                                                                        
sage: from sage.interfaces.giac import giac                                                                                                                                      
sage: result_giac = giac(sum_str)                                                                                                                                                
sage: result_giac                                                                                                                                                                
sum(F(sageVARx,sageVARy),sageVARx,sageVARa,sageVARb)
sage: y = var('y')                                                                                                                                                               
sage: result_giac.diff(y)                                                                                                                                                        
sum((diff(F,1))(sageVARx,sageVARy),sageVARx,sageVARa,sageVARb)

Let's try sympy:

sage: x,y = var("x,y")                                                                                                                                                           
sage: a,b = var("a,b")                                                                                                                                                           
sage: F = function("F")(x, y)                                                                                                                                                    
sage: expression,v,a,b = [expr._sympy_() for expr in (F, x, a, b)]                                                                                                               
sage: from sympy import summation                                                                                                                                                
sage: from sage.interfaces.sympy import sympy_init                                                                                                                               
sage: sympy_init()                                                                                                                                                               
sage: result_sympy = summation(expression, (v, a, b)).diff(y)                                                                                                                    
sage: result_sympy                                                                                                                                                               
Sum(Derivative(F(x, y), y), (x, a, b))

and an least maxima:

sage: s = maxima('sum(F(x,y),x,a,b)')                                                                                                                                            
sage: s                                                                                                                                                                          
'sum(F(x,y),x,a,b)
sage: s.diff('y')                                                                                                                                                                
'sum('diff(F(x,y),y,1),x,a,b)
sage: s.diff('y').sage()                                                                                                                                                         
sum(diff(F(x, y), y), x, a, b)

All OK, but just

s.sage().diff(var('y'))                                                                                                                                                    
diff(F(x, y), y)*D[0](sum)(F(x, y), x, a, b)

gives wrong answer.

I have understood what part of sage code gives this wrong result. This is function def dummy_diff(*args): from src/sage/calculus/calculus.py which is called from

def symbolic_expression_from_maxima_string(x, equals_sub=False, maxima=maxima):

Considering that

def symbolic_sum(expression, v, a, b, algorithm='maxima', hold=False):
    if algorithm == 'maxima':
        return maxima.sr_sum(expression,v,a,b)

I have provided example which reproduces the issue:

sage: from sage.misc.parser import Parser, LookupNameMaker 
....: from sage.calculus.calculus import _find_var, _find_func 
....: from sage.libs.pynac.pynac import symbol_table 
....: from sage.calculus.calculus import _is_function 
....:  
....: parser_make_Mvar = LookupNameMaker({}, fallback=lambda x: _find_var(x, interface='maxima')) 
....: parser_make_function = LookupNameMaker({}, fallback=_find_func) 
....: SRM_parser = Parser(make_int      = lambda x: SR(Integer(x)), 
....:                     make_float    = lambda x: SR(RealDoubleElement(x)), 
....:                     make_var      = parser_make_Mvar, 
....:                     make_function = parser_make_function) 
....:  
....: var_syms = {k: v for k, v in symbol_table.get('maxima', {}).items() 
....:             if not _is_function(v)} 
....: function_syms = {k: v for k, v in symbol_table.get('maxima', {}).items() 
....:                  if _is_function(v)} 
....:  
....: #from sage.calculus.calculus import dummy_diff 
....: def dummy_diff(*args): 
....:     """ 
....:     This function is called when 'diff' appears in a Maxima string. 
....:  
....:     EXAMPLES:: 
....:  
....:         sage: from sage.calculus.calculus import dummy_diff 
....:         sage: x,y = var('x,y') 
....:         sage: dummy_diff(sin(x*y), x, SR(2), y, SR(1)) 
....:         -x*y^2*cos(x*y) - 2*y*sin(x*y) 
....:  
....:     Here the function is used implicitly:: 
....:  
....:         sage: a = var('a') 
....:         sage: f = function('cr')(a) 
....:         sage: g = f.diff(a); g 
....:         diff(cr(a), a) 
....:     """ 
....:     f = args[0] 
....:     from sage.cpython.debug import type_debug, shortrepr 
....:     args = list(args[1:]) 
....:     for i in range(1, len(args), 2): 
....:         args[i] = Integer(args[i]) 
....:     print ("f", f) 
....:     print ("*args", *args) 
....:     res = f.diff(*args) 
....:     print("res", res) 
....:  
....:     return res 
....:  
....: function_syms['diff'] = dummy_diff                                                                                                                                         
sage:                                                                                                                                                                            
sage: s = "diff(sr_sum(F(_SAGE_VAR_x,_SAGE_VAR_y),_SAGE_VAR_x,a,b),_SAGE_VAR_y,1)" 
....:  
....: SRM_parser._variable_constructor().set_names(var_syms) 
....: SRM_parser._callable_constructor().set_names(function_syms) 
....: parser_output = SRM_parser.parse_sequence(s) 
....: print("parser_output", parser_output)                                                                                                                                      
f sr_sum(F(x, y), x, a, b)
*args y 1
res diff(F(x, y), y)*D[0](sr_sum)(F(x, y), x, a, b)
parser_output diff(F(x, y), y)*D[0](sr_sum)(F(x, y), x, a, b)

Inside dummy_diff(*args) type of f is just type(f) = <class 'sage.symbolic.expression.Expression'> and may be you are right that sage thinks that 'sr_sum' an unknown function in four variables.

nbruin commented 3 years ago
comment:4

You may have found which code in sage is involved in producing the wrong answer, but that code is not "at fault". It's just that it doesn't know how to differentiate sums, because it hasn't been told how to.

If one defines:

def sum_deriv(self,*args,**kwds):
    return self(*[[args[0].diff(kwds['diff_param'])]+list(args[1:])])
mysum=function('mysum',nargs=4,tderivative_func=sum_deriv)

one can just do:

sage: var('x,y,a,b')
(x, y, a, b)
sage: function('f')
f
sage: mysum(f(x,y),x,a,b).diff(y)
mysum(diff(f(x, y), y), x, a, b)

This approach is a little simple minded, of course (apart from the mathematical problem that term-wise differentiation is not always justified): it would need to check that the differentiation variable is indeed independent of the summation. With the current code, we get nonsense such as:

sage: mysum(f(x,y),x,a,b).diff(x)
mysum(diff(f(x, y), x), x, a, b)
sage: mysum(f(x,y),x,a,b).diff(a)
mysum(0, x, a, b)

However, it does show which hook on sage.functions.other.Function_sum' would need to be populated.

b7a46a19-303e-4ecf-84fd-1c9426f587e4 commented 3 years ago

Branch: u/gh-daju1/derivative_of_the_symbolic_sum_of_function_of_two_variables

b7a46a19-303e-4ecf-84fd-1c9426f587e4 commented 3 years ago
comment:6

Please see my commit which fixes the issue


New commits:

1130e20Trac #32161: adding "_tderivative_" method for Function_sum
b7a46a19-303e-4ecf-84fd-1c9426f587e4 commented 3 years ago

Commit: 1130e20

nbruin commented 3 years ago
comment:8

What does your code presently do with sum(f(x,b),x,a,b).diff(b)? It should probably check that the differentiation variable is independent from the bounds as well and perhaps throw an error if it's not.

I'm also not so sure that 0 is the best answer if a derivative with respect to the summation variable is requested. Sure, it's not a free variable, so an external reference to it can be interpreted as a "different" variable, but it' more likely a typo.

b7a46a19-303e-4ecf-84fd-1c9426f587e4 commented 3 years ago
comment:9

I have confirmed with sympy output

sage: x,y = var("x,y") 
....: a,b = var("a,b")                                                                                       
sage: F = function("F")(x, y)                                                                                
sage: expression,v,a,b = [expr._sympy_() for expr in (F, x, a, b)] 
....: from sympy import summation 
....: from sage.interfaces.sympy import sympy_init 
....: sympy_init()                                                                                           
sage: summation(expression, (v, a, b)).diff(y)                                                               
Sum(Derivative(F(x, y), y), (x, a, b))
sage: summation(expression, (v, a, b)).diff(v)                                                               
0
sage: summation(expression, (v, a, b)).diff(a)                                                               
Derivative(Sum(F(x, y), (x, a, b)), a)
sage: summation(expression, (v, a, b)).diff(b)                                                               
Derivative(Sum(F(x, y), (x, a, b)), b)

and tried the following

        from sage.calculus.functional import derivative
        if diff_param == a:
            ans = derivative(symbolic_sum(f, x, a, b, hold=True), a)
        if diff_param == b:
            ans = derivative(symbolic_sum(f, x, a, b, hold=True), b)

The problem is that with this test

            sage: s.diff(a)
            diff(sum(f, x, a, b), a)
            sage: s.diff(b)
            diff(sum(f, x, a, b), b)

I have received exception caused by infinitly recursive call-stack.

nbruin commented 3 years ago
comment:10

Replying to @daju1:

The problem is that with this test

            sage: s.diff(a)
            diff(sum(f, x, a, b), a)
            sage: s.diff(b)
            diff(sum(f, x, a, b), b)

I have received exception caused by infinitly recursive call-stack.

Yes, I guess the hold isn't strong enough here. You'll probably have to construct the derivative from lower-level primitives (basically, have a hold on the "derivative")

The problem is that in the current lingo, diff(sum(f, x, a, b), a) should basically return (D[2]sum)(f,x,a,b). The operator in question is easily constructed by sage.symbolic.operators.FDerivativeOperator(sage.functions.other.symbolic_sum,[2]), but I think as soon as it's evaluated at (f,x,a,b) it will try to expand the operator again, leading to an infinite recursion. I don't know if we have precedent of this: functions that do have some rules for expanding their derivatives, but still need inert forms to linger too. It would be worth asking sage symbolic computation experts (particularly the pynac crowd?) what means we have to support this.

For now I'd say raising an error would be fine; I don't think anything useful would be possible with a derivative with respect to a summation bound.

7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 3 years ago

Branch pushed to git repo; I updated commit sha1. New commits:

60f89e6Raise exception when trying to take a derivative of symbilic sum with respect to a summation bound
7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 3 years ago

Changed commit from 1130e20 to 60f89e6

7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 3 years ago

Branch pushed to git repo; I updated commit sha1. New commits:

f99433dMerge tag '9.4.rc2' into t/32161/derivative_of_the_symbolic_sum_of_function_of_two_variables
7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 3 years ago

Changed commit from 60f89e6 to f99433d

7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 3 years ago

Branch pushed to git repo; I updated commit sha1. New commits:

6afad65Trac #32161: testable examples extended
7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 3 years ago

Changed commit from f99433d to 6afad65

slel commented 3 years ago

Author: Alexey Drozdov

tscrim commented 3 years ago
comment:16

I think we should also be a bit careful if one of the summation limits is oo.

sage: n,x = var('n,x')
sage: F = function('F')(x)
sage: sum(F(x=x), x, 1, oo)
sum(F(x), x, 1, +Infinity)
sage: sum(x^(n*x), x, 1, oo)
sum(x^(n*x), x, 1, +Infinity)

Limits may not commute. While I think such a useful case for this is unlikely, we might want to be a little more on the cautious side here.

mkoeppe commented 2 years ago
comment:17

Stalled in needs_review or needs_info; likely won't make it into Sage 9.5.

nbruin commented 2 years ago
comment:19

This issue came up again in:

https://groups.google.com/g/sage-devel/c/Ksil-Qg9SlM/m/ZWUwKxdVAAAJ

perhaps merging a more modest solution first that just addresses the simple cases is worthwhile?

7822f248-ba56-45f1-ab3d-4de7482bdf9f commented 2 years ago
comment:20

Ahem...

I'm not sure that "derive a sum with respect to (one of) is bounds" has any meaning... set is a dscrete operation ; the summation variable takes integer values only. Forgetting this for a moment, try plotting points([(u, sum(x, x, 0, u)) for u in (0, 1/3..4)]) to convince yourselves. You can even plot(lambda u:sum(x, x, 0, u), (0, 4))...

In other words, sum(x, x, a, b) == -1/2*a^2 + 1/2*b^2 + 1/2*a + 1/2*b holds if and only if a and b are integers. Differentiating the right-hand side of this equality wrt a and b has a meaning ; differentiating the left hand has not.

The fact that the closed expression -1/2*a^2 + 1/2*b^2 + 1/2*a + 1/2*bdoes not convey this condition is a design insufficiency of Sage (and Giac, Sympy and Mathematica ; Fricas (Axiom) may be a horse of an entirely different color...)

In consequence, we can check if the differentiation variable occurs in the expression of bounds, and safely raise some "Nonsense exception" (to be carefully choosen...) if so.

HTH,

nbruin commented 2 years ago
comment:22

Replying to @EmmanuelCharpentier:

I'm not sure that "derive a sum with respect to (one of) is bounds" has any meaning... set is a dscrete operation ; the summation variable takes integer values only. Forgetting this for a moment, try plotting points([(u, sum(x, x, 0, u)) for u in (0, 1/3..4)]) to convince yourselves. You can even plot(lambda u:sum(x, x, 0, u), (0, 4))...

I don't think many interesting things will be done with it but something like F(x)=sum((x+k)^2,k,0,floor(sqrt(x)) makes perfect sense, and F(x) is a piecewise differentiable function. So I think the restraint the people considering the implementation here have shown discarding differentiation with respect to variables that occur in the bounds is somewhat warranted.

I also think we've seen it gets pretty hairy quickly, so for the sake of at least being able to deal with more straightforward cases (and avoiding the outright wrong results we're getting now!) we SHOULD probably just cast an error if the summation variable or any of the bounds contain the differentiation variable.