sagemath / sage

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

Parent methods tensor vs. tensor_product #30373

Open mkoeppe opened 4 years ago

mkoeppe commented 4 years ago

We unify the use of the methods tensor vs. tensor_product in parent classes.

Current situation:

Category ModulesWithBasis provides a parent method tensor to construct a tensor product of modules.

sage: C = CombinatorialFreeModule(QQ, ['x', 'y'])                                                                                                                
sage: C.tensor(C)                                                                                                                                                
Free module generated by {'x', 'y'} over Rational Field # Free module generated by {'x', 'y'} over Rational Field

It is not implemented completely for FreeModule

sage: V = FreeModule(QQ, 2)                                                                                                                                      
sage: V.tensor(V)                                                                                                                                                
AttributeError: type object 'FreeModule_ambient_field_with_category' has no attribute 'Tensor'
sage: 

In contrast, FiniteRankFreeModule (which is not in ModulesWithBasis) uses a parent method tensor to construct elements.

sage: F = FiniteRankFreeModule(QQ, 2)
sage: F.tensor((2, 2))                                                                                                                                           
Type-(2,2) tensor on the 2-dimensional vector space over the Rational Field

FilteredVectorSpace has both tensor (from ModulesWithBasis) and tensor_product, but only the latter works.

sage: FV = FilteredVectorSpace(2)                                                                                                                                
sage: FV.tensor_product(FV)                                                                                                                                      
QQ^4
sage: FV.tensor(FV)                                                                                                                                              
AttributeError: type object 'FilteredVectorSpace_class_with_category' has no attribute 'Tensor'

The same is true for FreeQuadraticModule_integer_symmetric:

sage: L = IntegralLattice(Matrix(ZZ, 2, [2,1,1,-2]))                                                                                                             
sage: L.tensor_product(L)                                                                                                                                        
Lattice of degree 4 and rank 4 over Integer Ring
Standard basis 
Inner product matrix:
[ 4  2  2  1]
[ 2 -4  1 -2]
[ 2  1 -4 -2]
[ 1 -2 -2  4]
sage: L.tensor(L)                                                                                                                                                
AttributeError: type object 'FreeQuadraticModule_integer_symmetric_with_categor' has no attribute 'Tensor'

The proposed solution in this ticket is to standardize on the method tensor_product, making tensor a deprecated alias.

Modules already has a tensor_square method, which tensor_product complements well.

We also add tensor_power.

No changes are made to the global tensor (unique instance of TensorProductFunctor).

Tickets:

See also: #18349

CC: @tscrim @egourgoulhon @mjungmath @jhpalmieri @fchapoton

Component: linear algebra

Author: Matthias Koeppe

Branch/Commit: u/mkoeppe/parent_methods_tensor_vs__tensor_product @ ed6a13f

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

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

Changed commit from 3d6f44e to c90acc6

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

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

c90acc6src/sage/modules/filtered_vector_space.py (FilteredVectorSpace_class.tensor_product): Check whether other is a filtered vector space
mkoeppe commented 2 years ago
comment:55

Here's an attempt

mkoeppe commented 2 years ago

Author: Matthais Koeppe

mkoeppe commented 2 years ago

Changed author from Matthais Koeppe to Matthias Koeppe

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

Changed commit from c90acc6 to b98c5df

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

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

b98c5dfModulesWithBasis.tensor_product: Fall back to trying to use the construction of the factors
7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 2 years ago

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

ed6a13fsrc/sage/categories/modules_with_basis.py: Update doctests
7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 2 years ago

Changed commit from b98c5df to ed6a13f

tscrim commented 2 years ago
comment:60

I don’t like this fallback for two reasons:

  1. It runs into the same problem with FilteredVectorSpace.tensor_product I gave in comment:40, it doesn’t know how it was built as a tensor product.
  2. (More of a worry since I haven’t checked it.) It returns an object that suggests there is less structure than their might be because of a lack of an implementation. Does this preserve the algebra structure when we take a tensor product of algebras?
mkoeppe commented 2 years ago
comment:61

Replying to Travis Scrimshaw:

I don’t like this fallback for two reasons:

  1. It runs into the same problem with FilteredVectorSpace.tensor_product I gave in comment:40, it doesn’t know how it was built as a tensor product.

The result doesn't have to know because it does not advertise itself as a tensor product.

tscrim commented 2 years ago
comment:62

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

I don’t like this fallback for two reasons:

  1. It runs into the same problem with FilteredVectorSpace.tensor_product I gave in comment:40, it doesn’t know how it was built as a tensor product.

The result doesn't have to know because it does not advertise itself as a tensor product.

That is the problem. If I build an object as a tensor product (via the tensor functor), then it should know that because I wanted it to know that explicitly. It should advertise itself as a tensor product.

tscrim commented 2 years ago
comment:63

Let me put it another way, the result is not in the codomain of the functor.

mkoeppe commented 2 years ago
comment:64

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

I don’t like this fallback for two reasons:

  1. It runs into the same problem with FilteredVectorSpace.tensor_product I gave in comment:40, it doesn’t know how it was built as a tensor product.

The result doesn't have to know because it does not advertise itself as a tensor product.

That is the problem. If I build an object as a tensor product (via the tensor functor), then it should know that because I wanted it to know that explicitly. It should advertise itself as a tensor product.

Suppose as a user I want to construct it anyway. What should I do then?

mkoeppe commented 2 years ago
comment:65

Replying to Travis Scrimshaw:

Let me put it another way, the result is not in the codomain of the functor.

I think you're conflating mathematical structure with implementation here.

tscrim commented 2 years ago
comment:66

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

I don’t like this fallback for two reasons:

  1. It runs into the same problem with FilteredVectorSpace.tensor_product I gave in comment:40, it doesn’t know how it was built as a tensor product.

The result doesn't have to know because it does not advertise itself as a tensor product.

That is the problem. If I build an object as a tensor product (via the tensor functor), then it should know that because I wanted it to know that explicitly. It should advertise itself as a tensor product.

Suppose as a user I want to construct it anyway. What should I do then?

The class needs to have its own hook separate from the hook that the functor uses.

tscrim commented 2 years ago
comment:67

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Let me put it another way, the result is not in the codomain of the functor.

I think you're conflating mathematical structure with implementation here.

How so? The result does not know it is a tensor product of modules, and there is no canonical way to deconstruct a vector space into a tensor product. For example, a 1-dimensional module could be a n-fold tensor product of itself n times. There are no canonical isomorphisms between these spaces. These are also not even isomorphic as graded vector spaces in general either (one needs a grading shift).

mkoeppe commented 2 years ago
comment:68

Mathematically an object is a tensor product if there exists a construction of it using the tensor functor.

In an implementation, we generally cannot know if something exists. An object is marked as a tensor product when the implementation has marked it as such. Like everything, this is subject to the implementation restrictions.

mkoeppe commented 2 years ago
comment:69

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

I don’t like this fallback for two reasons:

  1. It runs into the same problem with FilteredVectorSpace.tensor_product I gave in comment:40, it doesn’t know how it was built as a tensor product.

The result doesn't have to know because it does not advertise itself as a tensor product.

That is the problem. If I build an object as a tensor product (via the tensor functor), then it should know that because I wanted it to know that explicitly. It should advertise itself as a tensor product.

Suppose as a user I want to construct it anyway. What should I do then?

The class needs to have its own hook separate from the hook that the functor uses.

No, as a user. Suppose there is no such specific implementation. What should the user do?

tscrim commented 2 years ago
comment:70

Replying to Matthias Köppe:

Mathematically an object is a tensor product if there exists a construction of it using the tensor functor.

Then every object is a tensor product since we can always tensor with a 1-dimensional module. If we wanted to talk in that level of generality, there is no category of tensor products, Cartesean products, etc.

In an implementation, we generally cannot know if something exists. An object is marked as a tensor product when the implementation has marked it as such. Like everything, this is subject to the implementation restrictions.

Indeed, the category also contains some implementation requirements. (I would argue the tensor products category is the category with a distinguished tensor product construction, parallel to ModulesWithBasis.) Consequently, the result of the functor is promising certain implementation details with its codomain: such objects have methods via the category’s ParentMethods and provide an implementation of the corresponding @abstract_methods.

More concretely, if I had code that does:

T = tensor([V,W])
for F in T.tensor_factors():
     print(F)

This should work whenever T is constructed because the codomain is Cat.TensorProducts(), where Cat is the meet of the categories of V and W. However, with changing the name, the functor now would work but the code would fail with FilteredVectorSpaces.

tscrim commented 2 years ago
comment:71

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

I don’t like this fallback for two reasons:

  1. It runs into the same problem with FilteredVectorSpace.tensor_product I gave in comment:40, it doesn’t know how it was built as a tensor product.

The result doesn't have to know because it does not advertise itself as a tensor product.

That is the problem. If I build an object as a tensor product (via the tensor functor), then it should know that because I wanted it to know that explicitly. It should advertise itself as a tensor product.

Suppose as a user I want to construct it anyway. What should I do then?

The class needs to have its own hook separate from the hook that the functor uses.

No, as a user. Suppose there is no such specific implementation. What should the user do?

My guess is stop wishing for magical coding fairies and implement it. There is no generic way to give a tensor product the structure of a specific type of module. This essentially amounts to saying there is a canonical isomorphism between, e.g., R2 (x) R3 = R6. (Another version: How do you build a matrix from a tensor product of two matrices? There are two main ways to do this that are equivalent as they amount to choosing a different order of the isomorphism.) If a class Foo wants to know that a tensor product of two of itself can again be realized as an object Foo, then it needs to implement that and chose a specific isomorphism.

mkoeppe commented 2 years ago
comment:72

Replying to Travis Scrimshaw:

the result of the functor is promising certain implementation details with its codomain: such objects have methods via the category’s ParentMethods and provide an implementation of the corresponding @abstract_methods.

No, I think this is too strict to be useful.

mkoeppe commented 2 years ago
comment:73

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

No, as a user. Suppose there is no such specific implementation. What should the user do?

My guess is stop wishing for magical coding fairies and implement it.

No, no, users wouldn't like to be talked to like that.

mkoeppe commented 2 years ago
comment:74

We often want to identify 1-fold tensor products with the base module -- and the base module won't have the tensor_factors method. For example, this is what sage.tensor.modules and sage.manifolds.differentiable do.

tscrim commented 2 years ago
comment:75

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

the result of the functor is promising certain implementation details with its codomain: such objects have methods via the category’s ParentMethods and provide an implementation of the corresponding @abstract_methods.

No, I think this is too strict to be useful.

That an object in the codomain of a functor (or any morphism) must support what is required by that codomain (and should be an object such that X in Cat returns True)? I find this to actually be quite useful as I can expect certain properties to be there.

Consider the equivalent scenario: I have a function f: ZZ -> QQ[‘s’] that returned 0 as an integer and all non-zero elements as polynomials. You now have to write special checks for 0 rather than have it behave uniformly.

mkoeppe commented 2 years ago
comment:76

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

the result of the functor is promising certain implementation details with its codomain: such objects have methods via the category’s ParentMethods and provide an implementation of the corresponding @abstract_methods.

No, I think this is too strict to be useful.

That an object in the codomain of a functor (or any morphism) must support what is required by that codomain (and should be an object such that X in Cat returns True)? I find this to actually be quite useful as I can expect certain properties to be there.

There's always a tradeoff. Identifications such as the ones that I mentioned in comment:74 are also quite useful.

tscrim commented 2 years ago
comment:77

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

No, as a user. Suppose there is no such specific implementation. What should the user do?

My guess is stop wishing for magical coding fairies and implement it.

No, no, users wouldn't like to be talked to like that.

Of course I wouldn’t necessarily tell them in that way. However, unless somebody actually writes the code, then it doesn’t magically come into existence. Unfortunately, this is not the Dire Straights song “Money for Nothing”; Also Hell is full of 10 year olds who wished for exactly the same thing with the holophonor.

In this case, I believe it is impossible to implement in the level of generality you want because it amounts to making a canonical choice where there is not one.

mkoeppe commented 2 years ago
comment:78

Replying to Travis Scrimshaw:

There is no generic way to give a tensor product the structure of a specific type of module. This essentially amounts to saying there is a canonical isomorphism between, e.g., R2 (x) R3 = R6.

It does not have to be canonical to be useful. And in fact one wouldn't construct that but rather construct a free module whose basis is indexed by the cartesian product of the input index sets.

tscrim commented 2 years ago
comment:79

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

the result of the functor is promising certain implementation details with its codomain: such objects have methods via the category’s ParentMethods and provide an implementation of the corresponding @abstract_methods.

No, I think this is too strict to be useful.

That an object in the codomain of a functor (or any morphism) must support what is required by that codomain (and should be an object such that X in Cat returns True)? I find this to actually be quite useful as I can expect certain properties to be there.

There's always a tradeoff. Identifications such as the ones that I mentioned in comment:74 are also quite useful.

I agree that they are useful, but you need a canonical way to do it in general. The point is that you are making an identification based upon a specific choice of isomorphism. Individual classes are allowed to make that choice, which you are implicitly doing when you have either a as_foo() method or Foo(T) in your code.

tscrim commented 2 years ago
comment:80

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

There is no generic way to give a tensor product the structure of a specific type of module. This essentially amounts to saying there is a canonical isomorphism between, e.g., R2 (x) R3 = R6.

It does not have to be canonical to be useful. And in fact one wouldn't construct that but rather construct a free module whose basis is indexed by the cartesian product of the input index sets.

But that is doing what is mandated by the category. It is holding onto the knowledge of how it is constructed. What you are asking for is something to return the equivalent of R6.

mkoeppe commented 2 years ago
comment:81

I think in comment:79 you replied to the wrong comment.

tscrim commented 2 years ago
comment:82

Replying to Matthias Köppe:

I think in comment:79 you replied to the wrong comment.

It applies to both comment:76 and comment:74. I just chose the more recent one.

mkoeppe commented 2 years ago
comment:83

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

There is no generic way to give a tensor product the structure of a specific type of module. This essentially amounts to saying there is a canonical isomorphism between, e.g., R2 (x) R3 = R6.

It does not have to be canonical to be useful. And in fact one wouldn't construct that but rather construct a free module whose basis is indexed by the cartesian product of the input index sets.

But that is doing what is mandated by the category. It is holding onto the knowledge of how it is constructed. What you are asking for is something to return the equivalent of R6.

No, I don't think this resembles anything what I'm asking for. I think we've lost the context somewhere on the way here.

mkoeppe commented 2 years ago
comment:84

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

I think in comment:79 you replied to the wrong comment.

It applies to both comment:76 and comment:74. I just chose the more recent one.

Wait, you are concerned about the identification of a module M with its 1-fold tensor product not being canonical?

mkoeppe commented 2 years ago
comment:85

For the question of 1-fold tensor products, there's by the way an old ticket: #18349

tscrim commented 2 years ago
comment:86

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

There is no generic way to give a tensor product the structure of a specific type of module. This essentially amounts to saying there is a canonical isomorphism between, e.g., R2 (x) R3 = R6.

It does not have to be canonical to be useful. And in fact one wouldn't construct that but rather construct a free module whose basis is indexed by the cartesian product of the input index sets.

But that is doing what is mandated by the category. It is holding onto the knowledge of how it is constructed. What you are asking for is something to return the equivalent of R6.

No, I don't think this resembles anything what I'm asking for. I think we've lost the context somewhere on the way here.

Sorry, that was a bad phrasing on my part. You are asking for returning R6 to be okay, which you have had to make a (non-canonical) choice of a isomorphism that they user has no control over. The tensor product category has a special distinguished choice of construction as a tensor product, analogous to ModulesWithBasis being modules with a distinguished choice of a basis.

tscrim commented 2 years ago
comment:87

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

I think in comment:79 you replied to the wrong comment.

It applies to both comment:76 and comment:74. I just chose the more recent one.

Wait, you are concerned about the identification of a module M with its 1-fold tensor product not being canonical?

Not specifically for the 1-fold tensor products, but more generally. This code is expected to work for arbitrary tensor products with no special cases, right? (I can also tell you about the troubles I’ve had with PartitionTuples of length 1 versus Partitions, which motivates my desire for uniformity.)

mkoeppe commented 2 years ago
comment:88

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

There is no generic way to give a tensor product the structure of a specific type of module. This essentially amounts to saying there is a canonical isomorphism between, e.g., R2 (x) R3 = R6.

It does not have to be canonical to be useful. And in fact one wouldn't construct that but rather construct a free module whose basis is indexed by the cartesian product of the input index sets.

But that is doing what is mandated by the category. It is holding onto the knowledge of how it is constructed. What you are asking for is something to return the equivalent of R6.

No, I don't think this resembles anything what I'm asking for. I think we've lost the context somewhere on the way here.

Sorry, that was a bad phrasing on my part. You are asking for returning R6 to be okay

No

mkoeppe commented 2 years ago
comment:89

Replying to Travis Scrimshaw:

Unfortunately, this is not the Dire Straights song “Money for Nothing”

I do appreciate the attempt to engage in intergenerational dialogue

tscrim commented 2 years ago
comment:90

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

There is no generic way to give a tensor product the structure of a specific type of module. This essentially amounts to saying there is a canonical isomorphism between, e.g., R2 (x) R3 = R6.

It does not have to be canonical to be useful. And in fact one wouldn't construct that but rather construct a free module whose basis is indexed by the cartesian product of the input index sets.

But that is doing what is mandated by the category. It is holding onto the knowledge of how it is constructed. What you are asking for is something to return the equivalent of R6.

No, I don't think this resembles anything what I'm asking for. I think we've lost the context somewhere on the way here.

Sorry, that was a bad phrasing on my part. You are asking for returning R6 to be okay

No

Then if you rename the functor hook to tensor_product, then FilteredVectorSpace's method of that name would have a bug.

tscrim commented 2 years ago
comment:91

Replying to Matthias Köppe:

Replying to Travis Scrimshaw:

Unfortunately, this is not the Dire Straights song “Money for Nothing”

I do appreciate the attempt to engage in intergenerational dialogue

Actually, that is music I like. ;) Or should I say I appreciate that there's "No One Like You" that I appreciate to have these discussions with.

mkoeppe commented 2 years ago

Description changed:

--- 
+++ 
@@ -62,8 +62,7 @@

 Tickets:
 - #31276 Tensor Product Method for `FiniteRankFreeModule`
-
-Follow-up:
 - #34448 Put tensor modules of `FiniteRankFreeModule` in `Modules().TensorProducts()`
+- #34589 `VectorFieldModule`, `TensorFieldModule`, `DiffFormModule`: Add methods `tensor_product`, `tensor_power`, update category of `TensorFieldModule`

 See also: #18349