Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug in multiple tensor contractions with scalar result #32355

Closed
egourgoulhon opened this issue Aug 10, 2021 · 17 comments
Closed

Bug in multiple tensor contractions with scalar result #32355

egourgoulhon opened this issue Aug 10, 2021 · 17 comments

Comments

@egourgoulhon
Copy link
Member

Tensor contractions on multiple indices give wrong results when the outcome is a scalar and the index positions in each tensor do not have the same order. For instance, let us consider

sage: M = Manifold(2, 'M')                                                                          
sage: X.<x,y> = M.chart()                                                                           
sage: a = M.multivector_field(2)                                                                    
sage: a[0,1] = 1                                                                                    
sage: b = M.diff_form(2)                                                                            
sage: b[0,1] = 1                                                                                    

The double contraction aij bij is obtained via

sage: a.contract(0, 1, b, 0, 1).expr()                                                              
2
sage: (a['^ij']*b['_ij']).expr()  # equivalent to above                                                                 
2

which is correct. However, asking for the double contraction aij bji
leads to

sage: a.contract(0, 1, b, 1, 0).expr()  
2
sage: (a['^ij']*b['_ji']).expr()  # equivalent to above                                                                     
2

which is obviously wrong: the result should be -2, since b is antisymmetric.

If the outcome is not a scalar field, there is no issue:

sage: c = M.tensor_field(3, 0, antisym=(0,1))                                                       
sage: c[0,1,0] = 1                                                                                  
sage: c.contract(0, 1, b, 0, 1).display()  # correct result                                                        
2 ∂/∂x
sage: c.contract(0, 1, b, 1, 0).display()  # correct result                                                           
-2 ∂/∂x

This bug has been reported in https://ask.sagemath.org/question/58379.

CC: @tscrim @mjungmath @mkoeppe

Component: manifolds

Keywords: tensor contraction

Author: Eric Gourgoulhon

Branch/Commit: 48f5523

Reviewer: Michael Jung

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

@egourgoulhon

This comment has been minimized.

@egourgoulhon egourgoulhon changed the title Bug in multiple tensor contractions Bug in multiple tensor contractions with scalar result Aug 10, 2021
@egourgoulhon
Copy link
Member Author

comment:2

The culprit is line 2187 of src/sage/tensor/modules/comp.py:

    for ind in comp_for_contr.index_generator():
        res += self[[ind]] * other[[ind]]

ind should be reordered before being passed to other[[...]].

@egourgoulhon
Copy link
Member Author

comment:3

The bug is fixed in the above branch. Some slight clean up of the code for parallelized contractions has also been performed.


New commits:

26ba8f7Fix bug in Components.contract() (Trac #32355)

@egourgoulhon
Copy link
Member Author

@egourgoulhon
Copy link
Member Author

Commit: 26ba8f7

@egourgoulhon
Copy link
Member Author

Author: Eric Gourgoulhon

@mjungmath
Copy link

comment:5

Here are some comments.

  • I think you don't need to initialize a new component in

              comp_for_contr = Components(self._ring, self._frame, ncontr,
                                          start_index=self._sindex)

    and you can use self.index_generator() right away, no?

  • What about this?

    -            for ind_s in comp_for_contr.index_generator():
    -                ind_o = [None for i in range(ncontr)]
    -                for pos_s, pos_o in contractions:
    -                    ind_o[pos_o] = ind_s[pos_s]
    -                ind_pairs.append((ind_s, ind_o))
    +            for ind_s in self.index_generator():
    +                ind_o = [ind_s[pos_s] for pos_s, _ in sorted(contractions, key=itemgetter(1))]
    +                ind_pairs.append((ind_s, ind_o))

    You could also turn the outer for-loop into a list comprehension which would be faster but I reckon less well readable.

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Aug 13, 2021

Changed commit from 26ba8f7 to 3e5ac7d

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Aug 13, 2021

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

3e5ac7dImprovement in the fix for #32355

@egourgoulhon
Copy link
Member Author

comment:7

Replying to @mjungmath:

Here are some comments.

Thanks for taking a look into this.

  • I think you don't need to initialize a new component in

              comp_for_contr = Components(self._ring, self._frame, ncontr,
                                          start_index=self._sindex)

    and you can use self.index_generator() right away, no?

Yes indeed! (this is needed for the generic case, but not for the specific case of a scalar output). The latest commit implement this change.

  • What about this?

    -            for ind_s in comp_for_contr.index_generator():
    -                ind_o = [None for i in range(ncontr)]
    -                for pos_s, pos_o in contractions:
    -                    ind_o[pos_o] = ind_s[pos_s]
    -                ind_pairs.append((ind_s, ind_o))
    +            for ind_s in self.index_generator():
    +                ind_o = [ind_s[pos_s] for pos_s, _ in sorted(contractions, key=itemgetter(1))]
    +                ind_pairs.append((ind_s, ind_o))

I would prefer to keep the original version, since it is more efficient from a computational point of view: there is no need to sort a list. Granted: in most use cases, the list contains only a few elements and sorting shall be fast. So it is more a matter of taste...

@mjungmath
Copy link

comment:8

Replying to @egourgoulhon:

I would prefer to keep the original version, since it is more efficient from a computational point of view: there is no need to sort a list. Granted: in most use cases, the list contains only a few elements and sorting shall be fast. So it is more a matter of taste...

You're right, this is faster! Then I still have one last comment:

-                ind_o = [None for i in range(ncontr)]
+                ind_o = [None] * ncontr

This is indeed faster.

If that is done, and patchbot is happy -> positive review.

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Aug 14, 2021

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

48f5523Yet another improvement in the fix for #32355

@sagetrac-git
Copy link
Mannequin

sagetrac-git mannequin commented Aug 14, 2021

Changed commit from 3e5ac7d to 48f5523

@egourgoulhon
Copy link
Member Author

comment:10

Replying to @mjungmath:
Then I still have one last comment:

-                ind_o = [None for i in range(ncontr)]
+                ind_o = [None] * ncontr

Done in the above commit.

@mjungmath
Copy link

Reviewer: Michael Jung

@egourgoulhon
Copy link
Member Author

comment:12

Thank you for the review!

@mkoeppe mkoeppe modified the milestones: sage-9.4, sage-9.5 Aug 22, 2021
@vbraun
Copy link
Member

vbraun commented Sep 7, 2021

Changed branch from public/manifolds/contraction_bug-32355 to 48f5523

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants