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

Promote #3494

Open
wants to merge 4 commits into
base: development
Choose a base branch
from
Open

Promote #3494

wants to merge 4 commits into from

Conversation

pzinn
Copy link
Contributor

@pzinn pzinn commented Sep 23, 2024

This PR addresses some of the issues raised in #3184.
before:

i1 : R=QQ[x]

o1 = R

o1 : PolynomialRing

i2 : S=R[y]

o2 = S

o2 : PolynomialRing

i3 : 1/x+1/y
stdio:3:3:(3): error: expected pair to have a method for '+'

i4 : matrix{{1/x}}*matrix{{1/y}}
stdio:4:13:(3): error: maps over incompatible rings

i5 : promote(1/x_R,frac S)
stdio:5:7:(3): error: no method found for applying promote to:
                   1
     argument 1 :  - (of class frac R)
                   x
     argument 2 :  frac S

i6 : lift(1/x_S,frac R)
stdio:6:4:(3): error: no method found for applying lift to:
                   1
     argument 1 :  - (of class frac S)
                   x
     argument 2 :  frac R

and

i1 : R=QQ[x]

o1 = R

o1 : PolynomialRing

i2 : T=R**QQ[z]

o2 = T

o2 : PolynomialRing

i3 : x_R*z
stdio:3:3:(3): error: expected pair to have a method for '*'

after:

i1 : R=QQ[x]

o1 = R

o1 : PolynomialRing

i2 : S=R[y]

o2 = S

o2 : PolynomialRing

i3 : 1/x+1/y

     y + x
o3 = -----
      x*y

o3 : frac S

i4 : matrix{{1/x}}*matrix{{1/y}}

o4 = | 1/xy |

                    1             1
o4 : Matrix (frac S)  <-- (frac S)

i5 : promote(1/x_R,frac S)

     1
o5 = -
     x

o5 : frac S

i6 : lift(1/x_S,frac R)

     1
o6 = -
     x

o6 : frac R

and

i1 : R=QQ[x]

o1 = R

o1 : PolynomialRing

i2 : T=R**QQ[z]

o2 = T

o2 : PolynomialRing

i3 : x_R*z

o3 = x*z

o3 : T

In particular it introduces a new method setupPromote which makes it easy to (automatically) promote from A -> B given a map f: A -> B.

@@ -871,7 +871,7 @@ export {
"left",
"length",
"lift",
"liftable",
"isLiftable",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list is sorted, so this line should be next to other "is" symbols.

@@ -11,6 +11,21 @@ needs "matrix2.m2" -- for lift
-- new polynomial ring or quotient ring from old --
----------------------------------

-- automate promotion
promoteFromMap = method()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe call this setupPromotionMethods or something like that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And perhaps commonEngineRingInitializations should call it, similar to the lift methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, that was part of the idea that it could replace some of the existing code. I haven't looked into it yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about just setupPromote? so it's not too long

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you. My plan is to eventually allow the notation map(R, S) = f.

if liftable(m,ZZ) then lift(m,ZZ) else 0))
lift(m,ZZ,Verify=>false) ?? 0))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I understand the motivation in this change. I like ?? and ??= in certain situations (e.g. caching) but I'm concerned that in places like this it might make debugging more difficult. For instance, I imagine we would like to see an error if isLiftable returned true but lift failed here, because that would be a bug and an error would help us locate it, but ?? hides the error here and silently returns zero.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general I would agree with you but here's a pretty clear-cut case where we're basically calling lift twice for no reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm actually a little confused about what's happening in this code, but sure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought, basically any time someone calls isLiftable, a potential call to lift is on the horizon, no?

lift(QQ,QQ) := opts -> (r,QQ) -> r
liftable(QQ,QQ) := (QQ,QQ) -> true
isLiftable(QQ,QQ) := (QQ,QQ) -> true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this line even exist? We don't have isLiftable(ZZ,ZZ), etc.. Maybe there should be isLiftable(Ring, Ring) := (R, S) -> R === S that is used as a last resort if R and S don't have an isLiftable method installed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, this line is useless I believe.
right now for most (all?) rings we have lift(R,R):=o->(x,R)->x so isLiftable(R,R) will give true without any need to override the default behaviour.

@pzinn
Copy link
Contributor Author

pzinn commented Oct 3, 2024

there is isLiftable in Varieties.m2. should it be merged with the core isLiftable?

Comment on lines 95 to 97
RS := AB/image fg;
promoteFromMap map(RS,R,(vars AB)_{0 .. m-1});
if S =!= R then promoteFromMap map(RS,S,(vars AB)_{m .. m+n-1});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it natural that when R == S we favor the first ring map over the second?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this is annoying. no there isn't. in fact there are 3 choices here:

  • favour the first
  • favour the second
  • decide that it's ambiguous and not call promoteFromMap at all.

I'm not sure what's best.

Copy link
Member

@mahrud mahrud Oct 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there's a general lesson to be learned from the way NormalToricVarieties handles natural maps:

i1 : needsPackage "NormalToricVarieties";

i2 : X = toricProjectiveSpace 1;

i3 : Y = toricProjectiveSpace 2;

i4 : Z = X ** Y;

i5 : Z_[0]

o5 = | 1 |
     | 0 |
     | 0 |

o5 : ToricMap Z <--- X

i6 : Z_[1]

o6 = | 0 0 |
     | 1 0 |
     | 0 1 |

o6 : ToricMap Z <--- Y

i7 : Z^[1]

o7 = | 0 1 0 |
     | 0 0 1 |

o7 : ToricMap Y <--- Z

i8 : Z^[0]

o8 = | 1 0 0 |

o8 : ToricMap X <--- Z

I wonder if we could have a notation for specific ring maps. In particular, if X === Y then Z_[0] and Z_[1] are still clearly distinguished.

@@ -85,7 +83,7 @@ map(Ring, Ring, Matrix) := RingMap => opts -> (R, S, m) -> (
" into a degree of length ", toString degreeLength R);
opts.DegreeMap
)
else if workable (() -> promote({},S,R)) then (d -> first promote({d},S,R))
else if (pr:=lookup(promote,List,S,R)) =!= null then (d -> first pr({d},S,R))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, I didn't know promote(List,S,R) is for promoting the degree till now ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record I fixed this line because it could create infinite loops. It pretty much never occurred before because there were few promote, but now with the added flexibility of promoteFromMap (or whatever we're going to end up calling), it can easily happen.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would be in favor of deprecating promote(List, Ring, Ring) and instead given a ring map f make use of f.degreeMap and f.degreeLift.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe just degreeGroup f defined as degreeGroup RingMap := f -> f.degreeMap, which is similar to how NormalToricVarieties supports picardGroup ToricMap to give the map of Picard groups functorially.

@mahrud
Copy link
Member

mahrud commented Oct 3, 2024

there is isLiftable in Varieties.m2. should it be merged with the core isLiftable?

Sure, you just need to remove isLiftable = method() right? That should be fine.

@mahrud
Copy link
Member

mahrud commented Oct 3, 2024

A similar approach to your promoteFromMap that I thought about a while ago but never tried implementing is based on allowing users to install a natural ring map between any two given rings and having internal methods simply call map(R, S) to access it. e.g. I could define two completely different rings where I have a mathematically natural map between them, but defining a ring map and passing it to every function is really cumbersome, but if I could literally type:

map(R, S) := map(R, S, { ... }, DegreeLift => ..., DegreeMap => ...)

and have it cache that ring map (there is a syntax for allowing this), then promote and lift could also use it. Or perhaps we could allow something like:

lift(R, S) := ...
promote(S, R) := ...

to define these methods in one place and have it be used throughout.

The main motivation for this was having a better way to cache a ring map that specifically has a degree map or degree lift map that is then used here:

M2/M2/Macaulay2/m2/basis.m2

Lines 200 to 203 in 1fd2382

-- TODO: check that S is compatible; i.e. there is a map R <- S
-- perhaps the map should be given as the option instead?
S := if opts.SourceRing =!= null then opts.SourceRing else R;
phi := map(R, S);

In many situations map(R, S) is defined, but the degree map that M2 uses by default is incorrect, which causes all sorts of errors here.

@pzinn
Copy link
Contributor Author

pzinn commented Oct 3, 2024

I'm not sure if this is what you want but map(R,S) will work after calling promoteFromMap:

i1 : R=QQ[x]

o1 = R

o1 : PolynomialRing

i2 : S=QQ[y]

o2 = S

o2 : PolynomialRing

i3 : promoteFromMap map(R,S,{x^2})

i4 : map(R,S)

                  2
o4 = map (R, S, {x })

o4 : RingMap R <-- S

@pzinn pzinn force-pushed the promote branch 2 times, most recently from a87434d to a7e9349 Compare October 4, 2024 13:10
@pzinn
Copy link
Contributor Author

pzinn commented Oct 5, 2024

there is isLiftable in Varieties.m2. should it be merged with the core isLiftable?

Sure, you just need to remove isLiftable = method() right? That should be fine.

That seems to cause problems. I haven't looked in detail but the Core isLiftable is a method(TypicalValue => Boolean, Dispatch => {Thing, Type, Type}) whereas the one in Varieties isn't, so the isLiftable(Matrix, Matrix) there probably doesn't work.

@mahrud
Copy link
Member

mahrud commented Oct 5, 2024

Okay, then either change the method type or don't rename it?

@pzinn
Copy link
Contributor Author

pzinn commented Oct 6, 2024

not sure what you mean by either option. Do you want to change isLiftable(Matrix, Matrix) to take different arguments?

@mahrud
Copy link
Member

mahrud commented Oct 6, 2024

No, change the isLiftable in Core. For instance you could use hooks to check lift(r, R) =!= null by default:

isLiftable(Number,      Ring) :=
isLiftable(RingElement, Ring) := (r, R) -> ring r === R or tryHooks(
    LiftabilityHooks, (r, R), (r, R) -> lift(r, R) =!= null))

Then you only need to install the special cases where there's legitimately a way to check liftability without actually lifting like this:

addHook(LiftabilityHooks, (r, R) -> if ring r === QQ and R === ZZ then denominator r === 1)

Actually, I would love to use hooks like this for lift and promote and stop this mess:

i49 : #methods lift

o49 = 103

i50 : #methods promote

o50 = 124

@pzinn
Copy link
Contributor Author

pzinn commented Oct 6, 2024

I have to disagree with you here. I don't think hooks should be used as a replacement for the traditional method paradigm based on inheritance. And changing the definition of the Core isLiftable to not be Dispatch => {Thing, Type, Type} would be a major change which would need to be discussed with all involved developers. In any case, definitely outside the scope of this PR.

@mahrud
Copy link
Member

mahrud commented Oct 6, 2024

Okay, then don't rename liftable.

@pzinn
Copy link
Contributor Author

pzinn commented Oct 6, 2024

OK I can revert that part of the PR. Though at some point we'll want to do the rename, and then the problem will arise again.

@mahrud
Copy link
Member

mahrud commented Oct 6, 2024

Well, I provided a way for that to happen, but you disagreed, and I don't see another option.

I don't think hooks should be used as a replacement for the traditional method paradigm based on inheritance.

The problem with lift, promote and liftable is specifically that they do not use inheritance because e.g. QQ[x] is not a subtype of QQ, so why are they implemented as methods in the first place? The whole Dispatch => Thing system seems to be kind of a hack, given that rings and modules don't have inheritance whatsoever.

Besides, we use hooks in places where a function depends on the specifics of the ring of its inputs several places; look up ReduceHooks and ContainmentHooks for instance. This seems like the natural application to me.

@pzinn
Copy link
Contributor Author

pzinn commented Oct 6, 2024

I don't disagree that Dispatch is a bit of a hack. If M2internals weren't at 3am for me, I'd be happy to suggest a general discussion about it at some point.

@pzinn
Copy link
Contributor Author

pzinn commented Oct 6, 2024

reverting liftable -> isLiftable 7689295 e9e77da a7e9349

@pzinn pzinn force-pushed the promote branch 4 times, most recently from 04b5a89 to 53983f1 Compare October 7, 2024 06:48
@mahrud
Copy link
Member

mahrud commented Oct 7, 2024

Are you aiming for this PR to make it to the upcoming release, or the next one?

@pzinn
Copy link
Contributor Author

pzinn commented Oct 7, 2024

I think it should be ready for this release.

@pzinn pzinn force-pushed the promote branch 2 times, most recently from 9ce1531 to 1ff3f83 Compare October 7, 2024 23:16
@pzinn pzinn marked this pull request as ready for review October 8, 2024 04:31
@pzinn
Copy link
Contributor Author

pzinn commented Oct 8, 2024

seems I'm getting both #3239 and #1579 ?

--- author(s): PZJ
--- notes:

undocumented methods setupLift
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you undocument these rather than just list the method keys as secondary keys?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's because you get weird documentation errors, look at the Synopsis keyword used in the documentation of preimage, for instance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly out of laziness. Also, some of the methods are really meant for internal use only, so I need to decide whether to document those or not.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I would vote to keep both of these methods unexported for now (of course you can do importFrom_Core "setupPromote" in your own package to use it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I said "some of the methods are really meant for internal use only", I did not mean that setupPromote and setupLift were for internal use only. I meant some of their methods (as listed by say methods setupPromote) are.

Copy link
Member

@mahrud mahrud Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well you've listed two things under Usage, so they should be listed as keys.

Key
setupLift
Headline
automatic lift from one ring to another
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what sense is this "automatic"? Seems like the purpose of this function is to manually setup lifting method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops. copy-pasted too quickly the setupPromote doc.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My complaint applies there, too. How is that automatic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is automatic in the sense that you don't need to invoke promote explicitly, see the examples provided for setupPromote

Comment on lines +242 to +243
-- promote(Matrix,R,S) :=
-- promote(MutableMatrix,R,S) := -- doesn't work, cf https://github.com/Macaulay2/M2/issues/2192
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused about these commented lines. Don't you have these exact methods implemented two lines below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, but not as S ** M

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it be? That wouldn't even work because Ring ** Matrix calls Ring ** Module which then calls promote(Matrix, Ring), so it would be a loop.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hash changed again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a pain, we should set up the emacs stuff differently so it doesn't occur...

Key
setupPromote
Headline
automatic promotion from one ring to another
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your force push made my comment disappear, but still, I don't think this headline describes what this function does. If you don't want to remove "automatic", at least add "set up" to the beginning? Also:

  • list at least one method key
  • maybe link to this node from promote? (and similarly from lift to setupLift)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will rewrite doc

@@ -466,6 +503,8 @@ swap = (x,y) -> (y,x)
promoterightexact = swap @@ promoteleftexact @@ swap
promoterightinexact = swap @@ promoteleftinexact @@ swap

isPromotable = (R,S) -> lookup(promote,R,S) =!= null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be defined next to promote?

Comment on lines +253 to +257
lift(R,S) := opts -> (a,S) -> if opts.Verify then f a else try f a;
lift(List,R,S) := opts -> (m,R,S) -> apply(m,degmap);
lift(Module,R,S) := opts -> (M,R,S) -> S ** M;
lift(Matrix,R,S) := opts -> (m,R,S) -> map(lift(target m,S),lift(source m,S),applyTable(entries m,x->lift(x,S)));
lift(MutableMatrix,R,S) := opts -> (m,R,S) -> mutableMatrix applyTable(entries m,x->lift(x,S));
Copy link
Member

@mahrud mahrud Oct 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have any lift methods of matching e.g. lift(Module, R, S) that isn't defined here automatically? Because if not, I think all of these should just be e.g. lift(Module, Ring, Ring), and we can rename lift(x,S) to something else that is defined per ring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good question. lift(Module,...) only works for free modules anyway, it seems the definition I give could work universally. that being said it's not how it's done for the usual internal lift, and I don't dare to change too much.

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

Successfully merging this pull request may close these issues.

2 participants