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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement Adams operator for lazy power series #36396

Merged
merged 6 commits into from Feb 2, 2024

Conversation

fchapoton
Copy link
Contributor

@fchapoton fchapoton commented Oct 4, 2023

Here is a first sketch to implement Adams operators on Lazy formal power series.

also working in the multivariate case.

This will be useful to implement plethystic Exp and Log later.

馃摑 Checklist

  • The title is concise, informative, and self-explanatory.
  • The description explains in detail what this PR is about.
  • I have linked a relevant issue or discussion.
  • I have created tests covering the changes.
  • I have updated the documentation accordingly.

@fchapoton
Copy link
Contributor Author

@tscrim and @mantepse : would you please help to do that properly ?

@tscrim
Copy link
Collaborator

tscrim commented Oct 4, 2023

I think this implementation is mostly correct. For multivariate series, you would just need to pass multiple parameters, just like you would for polynomials. So something like

scaled = self(*[g**p for g in self.parent().gens()])

I think will work for all cases.

Also your test for variables in the base ring is not sufficient as ZZ.gens() returns (1,). I think you will need to explicitly call the subs and see if it works on base_ring.an_element().

This will also not work differently for a base ring of QQ['x']['y'] and QQ['x,y'] if I understand the code correctly. I think getting this to work right requires you to recurse through base rings until you find the one where subs() doesn't exist.

You can also do a shortcut for p = 1 to return self.

@mantepse
Copy link
Contributor

mantepse commented Oct 4, 2023

I think plethystic exponential and logarithm for lazy symmetric functions are already implemented in combinat/species/generating_series.py.

I am currently a bit confused why the Adams operator is an operator on (lazy) power series and not on (lazy) symmetric functions.

@fchapoton
Copy link
Contributor Author

Well, I want to look at symmetric functions acting by plethysm on lazy power series rings with polynomial coefficients. I think that there is currently no good way to do that in sage. Unless maybe by looking at symmetric functions with coefficients in lazy formal power series, which sound heavy.

@fchapoton
Copy link
Contributor Author

more generally, one could make symmetric functions acts on all rings endowed with Adams operators.

def func(cf):
return cf.subs(D)

return scaled.map_coefficients(func)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe turning around the logic is easier?

        scaled = self(*[g**p for g in self.parent().gens()])
        try:
            vars = self.base_ring().gens()
        except AttributeError:
            return scaled
        D = {v: v**p for v in vars}
        return scaled.map_coefficients(lambda c: c.subs(D))

Copy link
Collaborator

Choose a reason for hiding this comment

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

Let me again mention that basically all reasonable base rings have a gens() attribute. So the try-except block is effectively worthless (although perhaps it is okay to have it for the rare (if it even exists) commutative ring that does not implement gens()). The good news is that subs also works fine on things like ZZ and QQ. It introduces an inefficiency, but I doubt it is a big one.

@mantepse
Copy link
Contributor

mantepse commented Oct 4, 2023

@fchapoton: Out of curiousity: do you dislike the syntax A.<t, u> = LazyPowerSeriesRing(ZZ)?

Possibly you have overlooked #36396 (comment)

@mantepse mantepse closed this Oct 4, 2023
@mantepse mantepse reopened this Oct 4, 2023
@fchapoton
Copy link
Contributor Author

fchapoton commented Oct 4, 2023

yes, I do not like anything that requires the preparser to enter the game.

@mantepse
Copy link
Contributor

mantepse commented Oct 4, 2023

OK. What about the other comment - I don't understand the logic?

@fchapoton
Copy link
Contributor Author

well, the code has since changed according to Travis suggestions. I am not sure I understand you concern there.

@mantepse
Copy link
Contributor

mantepse commented Oct 4, 2023

I don't understand why you introduce the variable with_vars. Put differently, why don't you simply do

        stretched = self(*[g**p for g in self.parent().gens()])
        BR = self.base_ring()
        try:
            D = {v: v**p for v in BR.gens()}
            BR.one().subs(D)
        except AttributeError:
            return stretched
        
        return stretched.map_coefficients(lambda c: c.subs(D))

which is a bit shorter (in fact, half the length) and makes it - at least for me - much clearer what's happening.

In fact, it is also unclear to me which AttributeError the try-except should handle - that BR doesn't have gens (as, for example, a LazyDirichletSeriesRing, or the subs, or one...

This is the reason why I only put the vars = self.base_ring().gens() into the try-except.

(I also really dislike introducing the local func, it is much harder to read for me.)

@fchapoton
Copy link
Contributor Author

ok, I understand and changed accordingly to your suggestion. The try-except is there to check that we can perform the substitution. Maybe we should keep the gens() inside. For now, I have put it outside of the try.

Using lambda functions is not good practice according to pep8.

@mantepse
Copy link
Contributor

mantepse commented Oct 4, 2023

Could you point me to the location where pep8 says this? I could only find the statement that you should not bind lambda expressions to names.

@tscrim
Copy link
Collaborator

tscrim commented Oct 5, 2023

I doubt an AttributeError would ever (reasonably) be thrown as subs() is defined in Element. I am not sure how to construct a parent where that call would result in any kind of error. It would need to be on an element of a parent that defines ngens() with callable elements (that would not take variables as input). I just can't find one at present, but it is certainly possible one could (or would eventually) exist.

@mantepse
Copy link
Contributor

mantepse commented Oct 5, 2023

I think it is not a big issue, but (of course) this fails for lazy power series with coefficients being lazy Dirichlet series:

sage: D = LazyDirichletSeriesRing(QQ, "s")
sage: L.<z> = LazyPowerSeriesRing(D)
sage: s = L(constant=D(constant=1)); s
(1+1/(2^s)+1/(3^s)+O(1/(4^s))) + ((1+1/(2^s)+1/(3^s)+O(1/(4^s)))*z) + ((1+1/(2^s)+1/(3^s)+O(1/(4^s)))*z^2) + O(z^3)
sage: s.adams_operator(2)
...
AttributeError: 'LazyDirichletSeriesRing_with_category' object has no attribute 'gens'

It might even be a good thing to fail.

However, wouldn't it make more sense to check whether adams_operator exists on the coefficient ring?

sage: p = SymmetricFunctions(QQ).p()
sage: L.<z> = LazyPowerSeriesRing(p)
sage: s = L(lambda n: p[n]); s
p[] + p[1]*z + p[2]*z^2 + p[3]*z^3 + p[4]*z^4 + p[5]*z^5 + p[6]*z^6 + O(p[]*z^7)
sage: s.adams_operator(2)
...
AttributeError: 'SymmetricFunctionAlgebra_power_with_category' object has no attribute 'gens'

There is another problem: there are two methods on symmetric functions with almost the same name, but quite different definition: adams_operator and adams_operation. Shouldn't the LazyPowerSeries.adams_operator rather be called LazyPowerSeries.adams_operation - I think that this is what @fchapoton has in mind, right?

@fchapoton
Copy link
Contributor Author

fchapoton commented Oct 5, 2023

In the long term, one should use adams_operator everywhere. Maybe even make a category of Lambda-rings.
But for now, I want to make just a local code addition.

I do not see anything in the ring of symmetric functions:

gg "def.*adams_" src/sage
src/sage/categories/bialgebras_with_basis.py:        def adams_operator(self, n):
src/sage/combinat/root_system/weyl_characters.py:        def adams_operator(self, r):
src/sage/combinat/root_system/weyl_characters.py:        def _adams_operator_helper(self, r):
src/sage/groups/class_function.py:    def adams_operation(self, k):
src/sage/groups/class_function.py:    def adams_operation(self, k):
src/sage/rings/lazy_series.py:    def adams_operator(self, p):
src/sage/rings/polynomial/polynomial_element.pyx:    def adams_operator(self, n, monic=False):

EDIT: src/sage/combinat/sf/powersum.py: adams_operation = frobenius

@mantepse
Copy link
Contributor

mantepse commented Oct 5, 2023

The adams_operator comes from bialgebras_with_basis.py:

  • Compute the $n$-th convolution power of the identity morphism $\mathrm{Id}$ on self.

The adams_operation is an alias for frobenius in powersum.py

  • Return the image of the symmetric function self under the $n$-th Frobenius operator.

It seems to me that the LazyPowerSeries.adams_operation is closer to Frobenius.

sage: p = SymmetricFunctions(QQ).p()
sage: p[3].adams_operator(2)
2*p[3]
sage: p[3].adams_operation(2)
p[6]

@tscrim
Copy link
Collaborator

tscrim commented Oct 6, 2023

The alias adams_operation does not exist for all bases; I didn't see it for the Schur functions, only frobenius.

@mantepse
Copy link
Contributor

mantepse commented Oct 6, 2023

If I understand correctly, formal power series do not form a bialgebra in a natural way, right?

Still - shouldn't this method be called frobenius?

sage: p = SymmetricFunctions(QQ).p()
sage: p[2,1].expand(3)
x0^3 + x0^2*x1 + x0*x1^2 + x1^3 + x0^2*x2 + x1^2*x2 + x0*x2^2 + x1*x2^2 + x2^3

sage: p[2,1].frobenius(3)
p[6, 3]
sage: p[2,1].frobenius(3).expand(3)
x0^9 + x0^6*x1^3 + x0^3*x1^6 + x1^9 + x0^6*x2^3 + x1^6*x2^3 + x0^3*x2^6 + x1^3*x2^6 + x2^9

sage: p[2,1].adams_operator(3)
9*p[2, 1]
sage: p[2,1].adams_operator(3).expand(3)
9*x0^3 + 9*x0^2*x1 + 9*x0*x1^2 + 9*x1^3 + 9*x0^2*x2 + 9*x1^2*x2 + 9*x0*x2^2 + 9*x1*x2^2 + 9*x2^3

@fchapoton
Copy link
Contributor Author

We are talking about lambda ring structures. There are two natural lambda rings structures on the ring of symmetric functions. One acts on every homogeneous component, seen as the character ring of the group Sn. The other acts on the full ring, where tensor product comes from parabolic induction. They are both legitimate, and we cannot use the same name for both. So it's not simple to standardize the names.

@mantepse
Copy link
Contributor

mantepse commented Oct 6, 2023

Sorry, I am not fluent enough in the terminology, that's why I am asking. How are these two called in sfa.py currently?

I think that it would in fact be wonderful if you could include this background in the documentation.

At least superficially, frobenius seems closer to the new method, no?

@fchapoton
Copy link
Contributor Author

fchapoton commented Oct 9, 2023

I would prefer to keep the name I have chosen. Otherwise, one could use "inner_adams_operator" and "outer_adams_operator", but this is a bit awkward.

I am not eager to add explanations about Lambda rings, as long as we do not have the category of Lambda rings..

Is there anything else to fix ?

@mantepse
Copy link
Contributor

mantepse commented Oct 9, 2023

I just realized that very likely, sage.combinat.sf.sfa._variables_recursive is our friend. I do not see a good reason why variables in the coefficient ring should be treated differently from other variables. Of course include and exclude should then be passed along, because it is quite possible that I would like to have the variables in the coefficient ring not treated as degree one elements.

sage: P.<a,b,c> = QQ[]
sage: Q.<q,t> = P[]
sage: L.<z> = LazyPowerSeriesRing(Q)
sage: s = L(lambda n: (a + b + c)*q^n + t^n)
sage: s
(a+b+c+1) + (((a+b+c)*q+t)*z) + (((a+b+c)*q^2+t^2)*z^2) + (((a+b+c)*q^3+t^3)*z^3) + (((a+b+c)*q^4+t^4)*z^4) + (((a+b+c)*q^5+t^5)*z^5) + (((a+b+c)*q^6+t^6)*z^6) + O(z^7)
sage: s.adams_operator(2)
(a+b+c+1) + (((a+b+c)*q^2+t^2)*z^2) + (((a+b+c)*q^4+t^4)*z^4) + (((a+b+c)*q^6+t^6)*z^6) + O(z^7)

@mantepse
Copy link
Contributor

mantepse commented Oct 9, 2023

Could you explain to me why it shouldn't be called frobenius, and make the relation to adams_operator in bialgebras_with_basis precise?

I am not asking for documentation of $\lambda$-rings, I am asking for documentation about the reason for the chosen names - in particular, because I don't understand it.

@fchapoton
Copy link
Contributor Author

I am not sure this conversation is leading anywhere, but thanks for your time and attention. The Frobenius operator, in my mind, is taking something to the power p, where p is a prime number or a power of a prime number. On the other hand, I want to introduce a general mechanism for "plethystic actions of Sym", which will be calling "adams_operator" for the action of the symmetric function p_i (for any integer i) and then extend by multiplicativity.

@mantepse
Copy link
Contributor

mantepse commented Oct 11, 2023

So, would you agree to the following?

  • sage.combinat.sf.sfa.SymmetricFunctionAlgebra_generic_Element.frobenius should be called adams_operator,
  • sage.categories.bialgebras_with_basis.BialgebrasWithBasis.ElementMethods.adams_operator should be called something else
  • sage.combinat.sf.powersum.SymmetricFunctionAlgebra_power.Element.adams_operation (which is an alias for frobenius) should be deprecated.

I think it would be really bad to call something adams_operator which is in conflict with the naming in BialgebrasWithBasis without having a plan how to resolve the conflict.

PS: I just realize that in schemes and , more importantly, in polynomial_element, the adams_operator method does something different (but related), so maybe we really should stick to adams_operation instead of adams_operator.

@fchapoton
Copy link
Contributor Author

It's a quite a mess. I do not see an easy way out.

My point of view : every procedure A(i) defining a ring morphism for every positive integer i and such that A(i) o A(j) = A(i * j) deserves to be called an Adams operation acting on the ring. This holds

  • [A] for the "adams_operator" on polynomials
  • [B] for the "adams_operation" on class functions
  • [C] for my proposed procedure "adams_operator"
  • [D] for the "adams_operator" in the Weight ring
  • [E] for "frobenius" aka "adams_operation" in the ring of symmetric functions
  • [F] for "adams_operator" in bialgebras (but only if H is commutative)

Some of these things are related : [F] and [B] for the "inner Adams" and [E] and [C] for the "outer Adams".

So there is one notational conflict in symmetric functions, and maybe unhappy choices made in the past. Damn..

Copy link

Documentation preview for this PR (built with commit 9967f59; changes) is ready! 馃帀

Copy link
Contributor

@mantepse mantepse left a comment

Choose a reason for hiding this comment

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

Thank you for your patience!

@fchapoton fchapoton added the sd125 sage days 125 label Feb 1, 2024
@vbraun vbraun merged commit 2099947 into sagemath:develop Feb 2, 2024
19 of 20 checks passed
@fchapoton fchapoton deleted the lazy_adams branch February 3, 2024 08:18
@mkoeppe mkoeppe added this to the sage-10.3 milestone Mar 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants