sagemath / sage

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

Implement a hook to access free (graded) resolutions #34379

Closed tscrim closed 2 years ago

tscrim commented 2 years ago

In #33950, free (graded) resolutions for modules over polynomial rings were added. However, one needs to import a top-level function to do the construction. The goal of this ticket is to add a method, such as free_resolution(), to these (sub)modules (and ideals) to ease access.

Depends on #33950

CC: @kwankyu

Component: user interface

Author: Travis Scrimshaw

Branch/Commit: 6dba5e5

Reviewer: Kwankyu Lee

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

tscrim commented 2 years ago
comment:40

Replying to @kwankyu:

Replying to @tscrim:

In either case, it would still require some justification for why it is not simply a method of the class. As mentioned, the separation is actually the problem: The programmer should find within the class called the method doing the construction. (Of course, this is not a hard rule as there can be good reasons, typically due to code complexity.)

Code complexity is the justification. The constructor involves all subclasses of the base class. The programmer expects to find the constructor outside of(even in a different file, but not within) one of those classes.

If you were going to construct a FreeResolution, which returned something where isinstance(res, FreeResolution) is True, you would actually be looking at a different place for the construction than in FreeResolution? This is completely contrary with the behavior of __init__.

The sole reason to put the constructor inside the class via __classcall_private__ is to make isinstance feature work. You agreed with this. No?

Not with it being the sole reason; just a reason. It also associates the constructor (and any potential normalization of inputs) with the class itself. It also means you do not have to write duplicate documentation or have any question about where certain documentation aspects should go. It also makes it visible by, e.g., FreeResolution??.

Moreover it is hard to realize that the code defined in the method __classcall_private__ actually serves as a constructor. The new metaclass is explicit about this.

I agree that the name makes it somewhat difficult to see this without realizing that you have something with a ClasscallMetaclass for the uninitiated, including what the method __classcall_[private_]_ is suppose to do.

The problems I have with it are:

  1. It is duplicating what ClasscallMetaclass does with a different name.
  2. How do you resolve the issue of who-does-what when it also is, e.g., a UniqueRepresentation (both normalization and dispatch)?
  3. It is not scalable: You need a completely parallel tree of classcall metaclass subclass tree (e.g., ClasscallMetaclassInheritComparisons).

Because of the size of the duplication, (1) is a relatively small issue. However, it does have a code smell to me. (2) could be resolved with judicious and meticulous documentation, but I suspect it will just reinforce (1). (3) Right now, the number of classes is small, but it does create lots of extra maintenance burden for future developers (basically technical debt).

Since your biggest issue seems to be discoverability for a programmer with the name (which could potentially also be resolved in cases like this with some code comments, say, just before the class declaration or the __init__() method), perhaps we can add a feature to ClasscallMetaclass that also parses in __construction__ (using your experimental name; not 100% set on it yet, possible alternatives __dispatcher__ or __factory__).

Parent is unique in the whole sage library. It is irrelevant how Parent solves its own problem.

It is just an example. Right now I don't think we have any others, but this is something that has its uses (although there are likely better methods of doing this; this SO post talks a bit about them). However, what would you expect in such a case? In particular, why is that different than automatic dispatching to a subclass?

kwankyu commented 2 years ago
comment:41

Replying to @tscrim:

If you were going to construct a FreeResolution, which returned something where isinstance(res, FreeResolution) is True, you would actually be looking at a different place for the construction than in FreeResolution? This is completely contrary with the behavior of __init__.

By your idiom, we have "constructor + base class" (CB). It seems you see CB primarily as a constructor and I see CB as primarily a base class. This controversy itself indicates CB is a bad idiom. CB is against the principle of separation of concerns.

The sole reason to put the constructor inside the class via __classcall_private__ is to make isinstance feature work. You agreed with this. No?

Not with it being the sole reason; just a reason. It also associates the constructor (and any potential normalization of inputs) with the class itself. It also means you do not have to write duplicate documentation or have any question about where certain documentation aspects should go.

I don't see why it is desirable to associate the constructor and the base class by combining the code. There is no duplication in documentations. Actually in (CB), there is no place for either of them.

It also makes it visible by, e.g., FreeResolution??.

Hmm. This is a critical disadvantage of my new metaclass (MC). It will show the documentation of the base class. But this might be fixed.

As I originally suggested, I prefer the classical solution (CL):

def FreeResolution(...):  # constructor
    ...

class FreeResolution_base(...):  # base class
    ....

The sole reason I devised the new metaclass is to make isinstance feature work. (I think your idiom was first devised for the same purpose. No?) By the way, how important is this feature? This is for end users. I don't really see a use case. Is it important when working with combinatorial objects?

  1. How do you resolve the issue of who-does-what when it also is, e.g., a UniqueRepresentation (both normalization and dispatch)?
  2. It is not scalable: You need a completely parallel tree of classcall metaclass subclass tree (e.g., ClasscallMetaclassInheritComparisons).

Sorry. I don't really understand the issues. A simple-minded answer: do not use MC in problematic situations.

Since your biggest issue seems to be discoverability for a programmer with the name ...

My biggest issue is the presence of the definition of the constructor inside the base class. This looks strange and is hard to understand what is going on with the class. It will be the same when a developer sees it for the first time.

Parent is unique in the whole sage library. It is irrelevant how Parent solves its own problem.

It is just an example. Right now I don't think we have any others, but this is something that has its uses (although there are likely better methods of doing this; this SO post talks a bit about them). However, what would you expect in such a case? In particular, why is that different than automatic dispatching to a subclass?

The difference from what Parent does with __class__ is that the detailed code of the automatic dispatching (what a constructor does) is all visible inside the base class. (MC) works in the same way with (CB) but moves the dispatching code out of the base class.

tscrim commented 2 years ago
comment:42

Replying to @kwankyu:

Replying to @tscrim:

If you were going to construct a FreeResolution, which returned something where isinstance(res, FreeResolution) is True, you would actually be looking at a different place for the construction than in FreeResolution? This is completely contrary with the behavior of __init__.

By your idiom, we have "constructor + base class" (CB). It seems you see CB primarily as a constructor and I see CB as primarily a base class.

No, I see it as both because a base class should have a way to return an instance of itself based upon inputs. Good OOP says you don't care about the implementation details; in this case, the exact class is constructed. Python just doesn't have a good hook for this; __init__ is already working with a specific instance.

This controversy itself indicates CB is a bad idiom.

That is an absurd argument: Any controversy implies bad idiom.

CB is against the principle of separation of concerns.

This makes no sense. It is the concern of the object to construct itself.

From doing a bit of Google searching, the modern trend in a factory design pattern (which is the construction function/method here) when there is a common base case is to associate the factory object to the base class (a C++ example). This is what this idiom is doing with a bit of syntatic sugar to make it work through the usual __call__ protocol (by overriding that special method). We have just chosen a fairly generic name of __classcall__ (that reflects when the method is called) rather than something more specific like __construction__ or _factory_.

The sole reason to put the constructor inside the class via __classcall_private__ is to make isinstance feature work. You agreed with this. No?

Not with it being the sole reason; just a reason. It also associates the constructor (and any potential normalization of inputs) with the class itself. It also means you do not have to write duplicate documentation or have any question about where certain documentation aspects should go.

I don't see why it is desirable to associate the constructor and the base class by combining the code. There is no duplication in documentations.

Yes there is. If we take (CL) remember that the forward facing documentation is in the constructor function. So FreeResolution? gives that documentation. However, if we do the same for res?, we get the class documentation of FreeResolution_base. Thus, where should the description of the object go?

Another way to think of this, which is what I am getting at with overriding __class__, is that it is an upscale version of __init__ of the class. We at least agree that __init__ should be defined in the class, right?

Remember that subclasses are a very useful way of getting around having a bunch of if statements in the code deciding behavior of objects (and generally reduces the maintenance burden). This is basically why we have the two different classes for the Singular version and the free module version (or if we come along with a different implementation later on). They are implementation details that do not change the general model the code is representing, and associating the constructor with the base class is in line with that implementation too.

(I actually thought about splitting the resolution code up based upon the input being a module/ideal/matrix. However, I concluded that the tradeoff in complexity for doing that wasn't worth it.)

Actually in (CB), there is no place for either of them.

All documentation about the object should clearly go in the class level description of the base class. Furthermore, if you need to document specific inputs to the base class, that can go in the __init__ documentation, which is the natural place to look as it is describing its inputs.

It also makes it visible by, e.g., FreeResolution??.

Hmm. This is a critical disadvantage of my new metaclass (MC). It will show the documentation of the base class. But this might be fixed.

I am just guessing about your possible solutions to this, but the top one would be to copy the base class's doc. However, you would also have to deal with the raw code (where there would be a disassociation with the documentation), doctests (how would I associate a particular doctest with the constructor versus the base class), and how we measure coverage (related also with how many duplicate tests would this yield). Although I probably shouldn't comment too much until there is an actual proposal.

As I originally suggested, I prefer the classical solution (CL): The sole reason I devised the new metaclass is to make isinstance feature work. (I think your idiom was first devised for the same purpose. No?)

AFAIK, the ClasscallMetaclass was originally for normalizing inputs to CachedRepresentation. Although it was soon applied to handle construction (and possibly dispatch) of element classes, such as Tableau, without having to construct an explicit parent. This was mostly used for combinatorial objects early on.

By the way, how important is this feature? This is for end users. I don't really see a use case. Is it important when working with combinatorial objects?

It is also useful for things like algebras that have different implementations based upon their inputs. Polynomial rings are a great example of this (but don't currently implement this idiom). For instance, you could have code like

def foo(R=None):
    if R is None:
        R = PolynomialRing(QQ, 'x,y')
    if isinstance(R, PolynomialRing_general):
        return 1
    return 2

From a naïve look, why would the default argument for R lead to case 1? It seems like they should be different. By being separate, the constructor is not under even a tacit promise to return a subclass of PolynomialRing_general. In fact, this is true in this example as there is no common ABC for univariate and multivariate polynomial rings (which has caused me some headaches).

You also have to rename something when there is a conflict between the name of the base class and the factory name:

sage: import_statements(matrix)
# ** Warning **: several names for that object: Matrix, matrix
from sage.matrix.constructor import Matrix
sage: from sage.structure.element import Matrix  # not the constructor!

Hence, it would make it easier on the developer who has to use the class.

  1. How do you resolve the issue of who-does-what when it also is, e.g., a UniqueRepresentation (both normalization and dispatch)?
  2. It is not scalable: You need a completely parallel tree of classcall metaclass subclass tree (e.g., ClasscallMetaclassInheritComparisons).

Sorry. I don't really understand the issues. A simple-minded answer: do not use MC in problematic situations.

Please define what you mean by a problematic situation. It seems like you are wanting to say it is a case when there is a conflict between your new metaclass and UniqueRepresentation (or anything else that uses one of the other metaclasses). This is not a feasible option as we need to support both behaviors. Your proposal is creating issues where there were none previously.

Since your biggest issue seems to be discoverability for a programmer with the name ...

My biggest issue is the presence of the definition of the constructor inside the base class. This looks strange and is hard to understand what is going on with the class. It will be the same when a developer sees it for the first time.

That is true for basically any idiom, especially the project-specific ones. (As I mentioned above, this is not so Sage specific, mostly just the name __classcall_private__.) For instance, UniqueRepresentation. I agree that a new Sage developer who is an experienced programmer will have to learn something; there is no perfect solution. However, this idiom is easy to teach, simple, flexible, and has many natural aspects.

Parent is unique in the whole sage library. It is irrelevant how Parent solves its own problem.

It is just an example. Right now I don't think we have any others, but this is something that has its uses (although there are likely better methods of doing this; this SO post talks a bit about them). However, what would you expect in such a case? In particular, why is that different than automatic dispatching to a subclass?

The difference from what Parent does with __class__ is that the detailed code of the automatic dispatching (what a constructor does) is all visible inside the base class. (MC) works in the same way with (CB) but moves the dispatching code out of the base class.

I don't understand this. The base class is the one that chooses which instance to build and ultimately dispatches out to that __init__. There is no initialization or construction dispatching done with Parent setting __class__ to a dynamic subclass built from the category (in particular, it never goes back down or calls any other __init__ lower that Parent in its MRO). In fact, that the constructor code becoming visible somewhere in the MRO with the (CB) method is what you want, right?

kwankyu commented 2 years ago
comment:43

Replying to @tscrim:

because a base class should have a way to return an instance of itself based upon inputs.

But not of its subclass. It looks like that in your idiom.

Sorry. I feel tired of this discussion. I can't follow your arguments and I don't want to repeat my arguments.

As least, your idiom works. My complaint is only about the organization of the code. I will revert back the branch to your idiom.

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

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

cd0b948Revert to `__classcall_private__` idiom
d0e11a3Some edits
22dd075Nonhomogeneous examples
e5be7e4Merge branch 'develop'
22f74a2Remove ConstructorBaseclassMetaclass
609b40dRecover ClasscallMetaclass
b939bbcFix in projective_morphism.py
1622605Fix of _repr_()
81d6afbRecover errorneously deleted constant_function.pyx
d8bc44fMinor fixes
7ed8c4ca-6d56-4ae9-953a-41e42b4ed313 commented 2 years ago

Changed commit from b763155 to d8bc44f

kwankyu commented 2 years ago
comment:45

Mostly minor edits. I modified or deleted some examples of graded free resolutions with non-homogeneous inputs.

Otherwise looks good to me.

kwankyu commented 2 years ago

Reviewer: Kwankyu Lee

tscrim commented 2 years ago
comment:47

Thank you.

The only thing I am not sure about is the change for the resolution in free module handling things rather than FreeResolution. This also includes my implementation for the multipolynomial ideals (I think at the time, I didn’t have a single such entry point). We are duplicating some of the logic there, so there is more work to be done if another implementation is provided (in particular, if it expands the capabilities).

This isn’t really a big thing as we can revisit it should additional implementations be provided. I just wanted to see what you thought about this.

kwankyu commented 2 years ago
comment:48

Replying to @tscrim:

Thank you.

The only thing I am not sure about is the change for the resolution in free module handling things rather than FreeResolution.

Like your M.graded_free_resolution(), the dispatching to specific free resolution subclasses (those with _singular and _free_module postfixes) should happen in M.free_resolution() as this method has information about M. I regard these methods as the primary dispatcher to the free resolution subclasses.

Then there is your FreeResolution dispatcher (I will not use the term "constructor" as, it seems, we understand the term differently) inside FreeResolution base class. I regard this as a secondary way to use the free resolution subclasses. Hence your dispatcher may use the methods M.[graded_]free_resolution() depending on the type of the input M. Then there would be no duplication of code.

... my implementation for the multipolynomial ideals (I think at the time, I didn’t have a single such entry point).

I don't understand this. Would you name it explicitly? Where is it?

tscrim commented 2 years ago
comment:49

Replying to @kwankyu:

Replying to @tscrim:

Thank you.

The only thing I am not sure about is the change for the resolution in free module handling things rather than FreeResolution.

Like your M.graded_free_resolution(), the dispatching to specific free resolution subclasses (those with _singular and _free_module postfixes) should happen in M.free_resolution() as this method has information about M. I regard these methods as the primary dispatcher to the free resolution subclasses.

Your current proposal is not a good programming practice as it is not scalable. More concretely, you now have at least two different ways (that are not linked) for creating the free resolutions from, e.g., a module. Now you need to keep them consistent, which can be tedious as the code grows.

Then there is your FreeResolution dispatcher (I will not use the term "constructor" as, it seems, we understand the term differently) inside FreeResolution base class. I regard this as a secondary way to use the free resolution subclasses. Hence your dispatcher may use the methods M.[graded_]free_resolution() depending on the type of the input M. Then there would be no duplication of code.

This would be good with me. So we would have

def __classcall_private__(cls, M, *args, **kwds):
    try:
        return M.free_resolution(*args, **kwds)
    except AttributeError:
        raise ValueError(“unable to construct a free resolution”)

It is just right now we are duplicating the code. You will also need to add a [graded_]free_resolution() method to matrices.

The reason I didn’t chose this approach was because there would still be some code duplication and scalability to address since all of these inputs types are treated as a module. For instance, if I implement a way to compute resolutions using a polynomial ring over an arbitrary field (not just ones that Singular can recognize). Then we need to update all of the different free resolution methods to allow for this more general case. However your proposal is reasonable and isn’t (yet) so unwieldy that it is a significant burden, so let’s go with it.

... my implementation for the multipolynomial ideals (I think at the time, I didn’t have a single such entry point).

I don't understand this. Would you name it explicitly? Where is it?

Clearly the main entry point was designed to be FreeResolution.

kwankyu commented 2 years ago
comment:50

Replying to @tscrim:

You will also need to add a [graded_]free_resolution() method to matrices.

Free resolutions are about modules (ideals are also modules). Matrix is one way to specify a module. You don't need to attach the methods to matrices. I would not.

tscrim commented 2 years ago
comment:51

Replying to @kwankyu:

Replying to @tscrim:

You will also need to add a [graded_]free_resolution() method to matrices.

Free resolutions are about modules (ideals are also modules). Matrix is one way to specify a module. You don't need to attach the methods to matrices. I would not.

Then we should disallow a matrix as input altogether.

kwankyu commented 2 years ago
comment:52

Replying to @tscrim:

Replying to @kwankyu:

Replying to @tscrim:

You will also need to add a [graded_]free_resolution() method to matrices.

Free resolutions are about modules (ideals are also modules). Matrix is one way to specify a module. You don't need to attach the methods to matrices. I would not.

Then we should disallow a matrix as input altogether.

You may do so for FreeResolution dispatcher. But please do not remove it from the free resolution classes for internal use. By allowing a matrix as input to our free resolution classes, we may remove a small amount of conversion overhead (matrix -> module -> matrix) in those not-uncommon cases that we have a matrix on our hand.

tscrim commented 2 years ago
comment:53

I think you’re trying to have it both ways with matrices being automatically being treated as submodules (for an insignificant optimization (relative to the main computations) at the cost of code complexity). However, what I will do is have special handling within the FreeResolution hook because being consistent across the inputs is important to me and I want to do what I can to accommodate your viewpoints.

kwankyu commented 2 years ago
comment:54

Replying to @tscrim:

I think you’re trying to have it both ways with matrices being automatically being treated as submodules (for an insignificant optimization (relative to the main computations)

Yes.

at the cost of code complexity.

As I see it, the code complexity is also insignificant, and the code is already there.

what I will do is have special handling within the FreeResolution hook because being consistent across the inputs is important to me.

Okay. Then I won't insist on the matrix input if, as you see it, it would be a maintenance burden for your work with free resolution classes.

kwankyu commented 2 years ago
comment:55

Are you working on this or waiting for review as it is?

tscrim commented 2 years ago
comment:56

Sorry, I have been busy with family stuff and writing grants, and I did not have access to my laptop. Right now Sage is building on my laptop, and I plan to make the change after that is done. Give me a few more hours.

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

Changed commit from d8bc44f to 5c35ae5

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

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

01ed910Using the free_resolution() hook to let each class decide what to build.
35a9779Using the hook in projective morphism.
5c35ae5Making singular.pyx full coverage.
tscrim commented 2 years ago
comment:58

Here we go. I did was we agreed upon: the FreeResolution dispatches out to [graded_]free_resolution() with special handling for matrices.

I changed the projective morphism to use the hook.

I also fixed some an issue with the previous ticket that I should have caught: the singular.pyx has full coverage now.

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

Changed commit from 5c35ae5 to 250c5cb

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

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

250c5cbSome edits
kwankyu commented 2 years ago
comment:60

In FreeModule, why do you check if the module is a free module only when module is a matrix? You wanted to provide free resolutions for free modules.

In sage.rings.ideal, you provided free_resolution() hook. In this context, an ideal is of a general commutative ring, but your code seems to assume that the ring is a univariate polynomial ring over a field. You need to check this first. If the ring is other than that, raise a NotImplemented error.

tscrim commented 2 years ago
comment:61

Replying to Kwankyu Lee:

In FreeModule, why do you check if the module is a free module only when module is a matrix? You wanted to provide free resolutions for free modules.

This is what we agreed on: special check for matrices because you wanted the code to handle it but not add a method to the matrix classes. Thus, the main entry point needs to process it. Everything else is delegated to free_resolution().

In sage.rings.ideal, you provided free_resolution() hook. In this context, an ideal is of a general commutative ring, but your code seems to assume that the ring is a univariate polynomial ring over a field. You need to check this first. If the ring is other than that, raise a NotImplemented error.

I require the ideal is principal, which is automatically free because it has a single generator. This case holds for all commutative rings, right? All other cases will raise an NotImplementedError either via the is_principal() call or from the result being False.

kwankyu commented 2 years ago
comment:62

Replying to Travis Scrimshaw:

In sage.rings.ideal, you provided free_resolution() hook. In this context, an ideal is of a general commutative ring, but your code seems to assume that the ring is a univariate polynomial ring over a field. You need to check this first. If the ring is other than that, raise a NotImplemented error.

I require the ideal is principal, which is automatically free because it has a single generator. This case holds for all commutative rings, right?

Yes.

All other cases will raise an NotImplementedError either via the is_principal() call or from the result being False.

I see. Okay.

tscrim commented 2 years ago
comment:63

If you’re good with my changes and the patchbot comes back green (or you are confident with the appropriate tests being run), then positive review?

kwankyu commented 2 years ago
comment:64

Replying to Travis Scrimshaw:

Replying to Kwankyu Lee:

In FreeModule, why do you check if the module is a free module only when module is a matrix? You wanted to provide free resolutions for free modules.

This is what we agreed on: special check for matrices because you wanted the code to handle it

No. I wanted that the matrix input is kept in (__init__() of) free resolution subclasses. But I even agreed to remove it if you want because of maintenance burden.

For FreeResolution dispatcher, I think that not accepting matrix input is the right way. I think I agreed upon this...

kwankyu commented 2 years ago
comment:65

You removed

        if isinstance(module, Ideal_generic):
            S = module.ring()
            if len(module.gens()) == 1 and S in IntegralDomains():
                is_free_module = True
        elif isinstance(module, Module_free_ambient):
            S = module.base_ring()
            if (S in PrincipalIdealDomains()
                or isinstance(module, FreeModule_generic)):
                is_free_module = True 

from FreeResolution dispatcher, are these cases handled in free_resolution()? Or you decided not to handle those cases?

tscrim commented 2 years ago
comment:66

Replying to Kwankyu Lee:

Replying to Travis Scrimshaw:

Replying to Kwankyu Lee:

In FreeModule, why do you check if the module is a free module only when module is a matrix? You wanted to provide free resolutions for free modules.

This is what we agreed on: special check for matrices because you wanted the code to handle it

No. I wanted that the matrix input is kept in (__init__() of) free resolution subclasses. But I even agreed to remove it if you want because of maintenance burden.

This isn't really that much of a burden, especially since we don't fundamentally want to work with the module but the underlying matrix. We have a good justification for the special case of matrices.

For FreeResolution dispatcher, I think that not accepting matrix input is the right way. I think I agreed upon this...

If the class accepts a matrix, then the dispatcher should be able to handle the matrix. I think it would be really strange to have this discrepancy. Plus the constructor does some things to sanitize the input; in particular, it makes sure the matrix is immutable as this can lead to some very subtle bugs that are very hard to track down. (For example, you pass a mutable matrix into the constructor, compute the resolution, mutate the matrix; now the resolution does not necessarily match the input matrix.)

Actually, this makes me think of a case it should also handle: a free resolution input (where it would just return the input up to maybe stripping/adding the grading).

tscrim commented 2 years ago
comment:67

Replying to Kwankyu Lee:

You removed

        if isinstance(module, Ideal_generic):
            S = module.ring()
            if len(module.gens()) == 1 and S in IntegralDomains():
                is_free_module = True
        elif isinstance(module, Module_free_ambient):
            S = module.base_ring()
            if (S in PrincipalIdealDomains()
                or isinstance(module, FreeModule_generic)):
                is_free_module = True 

from FreeResolution dispatcher, are these cases handled in free_resolution()? Or you decided not to handle those cases?

These are handled by the respective free_resolution() methods in, e.g., ideal.

kwankyu commented 2 years ago
comment:69

Replying to Travis Scrimshaw:

Actually, this makes me think of a case it should also handle: a free resolution input (where it would just return the input up to maybe stripping/adding the grading).

Why? This is strange. PolynomialRing(PolynomialRing(QQ)) does not work in that way.

kwankyu commented 2 years ago
comment:70

trac is buggy...

tscrim commented 2 years ago
comment:71

Replying to Kwankyu Lee:

Replying to Travis Scrimshaw:

Actually, this makes me think of a case it should also handle: a free resolution input (where it would just return the input up to maybe stripping/adding the grading).

Why? This is strange. PolynomialRing(PolynomialRing(QQ)) does not work in that way.

Right, that was dumb of me. I was thinking it was more Element-like (e.g., Partition), but it is Parent-like (or function-like).

Do you think it would be useful to implement a method in graded free resolutions like as_ungraded() that forgets the grading?

kwankyu commented 2 years ago
comment:72

Replying to Travis Scrimshaw:

Do you think it would be useful to implement a method in graded free resolutions like as_ungraded() that forgets the grading?

No as far as I know. Let's leave that as a future work for who needs it.

kwankyu commented 2 years ago
comment:73

Now let this go.

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

Changed commit from 250c5cb to 6dba5e5

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

Branch pushed to git repo; I updated commit sha1 and set ticket back to needs_review. New commits:

6dba5e5Hot fixes
tscrim commented 2 years ago
comment:76

Thank you. This improved the code from my original proposal.

It took me a minute to realize that this is equivalent to the way I am used to seeing the definition of a resolution with 0 <- M <- F_1 <- .... However, this is definitely the better way to present this given the string representation.

kwankyu commented 2 years ago
comment:77

Replying to Travis Scrimshaw:

Thank you. This improved the code from my original proposal.

Thank you for the work.

If we want to be more mathematical, matrix input may be entirely removed. But we may regard matrix input as an added conveniency feature. I am happy in either way.

It took me a minute to realize that this is equivalent to the way I am used to seeing the definition of a resolution with 0 <- M <- F_1 <- .... However, this is definitely the better way to present this given the string representation.

A resolution of M is also said to be a resolution of F_1/M. See pages 22-23 of

https://faculty.math.illinois.edu/Macaulay2/Book/ComputationsBook/book/book.pdf

It seems that "resolution of M" is more conventional, but I think "resolution of F_1/M" is more reasonable. If you want to augment F_1 <- ... <- 0 at the left to get an exact sequence, that should be

0 <- F_1/M <- F_1 <- ... <- 0

where the canonical map F_1/M <- F_1 is our 0-th differential map and F_1/M is our target().

vbraun commented 2 years ago

Changed branch from public/rings/free_gr_res_hook-34379 to 6dba5e5