-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
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
Implementation of Powers of CRootOf
#27154
base: master
Are you sure you want to change the base?
Conversation
I have implemented the power function for `CRootOf`. For example, `r = CRootOf(x**3 + x + 1, 0)` is one of the roots of the equation $x^3 + x + 1 = 0$. Since $x^3 = -x - 1$, `r**3` can be calculated as `-r - 1`. Similarly, the powers of `CRootOf` can be transformed into a polynomial of degree less than the original polynomial.
✅ Hi, I am the SymPy bot. I'm here to help you write a release notes entry. Please read the guide on how to write release notes. Your release notes are in good order. Here is what the release notes will look like: This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.14. Click here to see the pull request description that was parsed.
|
sympy/polys/rootoftools.py
Outdated
d = degree(self.expr) | ||
if other < d: | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would also be possible to handle negative powers when the RootOf poly is irreducible over QQ:
In [6]: r = RootOf(z**3 + z + 29, 0)
In [7]: r
Out[7]:
⎛ 3 ⎞
CRootOf⎝z + z + 29, 0⎠
In [8]: e = 1/r
In [9]: e
Out[9]:
1
───────────────────────
⎛ 3 ⎞
CRootOf⎝z + z + 29, 0⎠
In [10]: N(e)
Out[10]: -0.337396969245337
In [11]: invert(z, z**3 + z + 29)
Out[11]:
2
z 1
- ── - ──
29 29
In [12]: invert(z, z**3 + z + 29).subs(z, r)
Out[12]:
2
⎛ 3 ⎞
CRootOf⎝z + z + 29, 0⎠ 1
- ──────────────────────── - ──
29 29
In [13]: invert(z, z**3 + z + 29).subs(z, r).n()
Out[13]: -0.337396969245337
sympy/polys/rootoftools.py
Outdated
_x = list(self.expr.free_symbols)[0] | ||
_expr = expr = (LT(self.expr) - self.expr) / LC(self.expr) | ||
for _ in range(other - d): | ||
_expr = (_x*_expr).expand() | ||
if degree(_expr) >= d: | ||
_expr += (LC(_expr) * expr).expand() - LT(_expr) | ||
return _expr.subs({_x: self}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems a bit unnecessarily complicated:
In [17]: r = RootOf(z**3 + z + 29, 0)
In [18]: r
Out[18]:
⎛ 3 ⎞
CRootOf⎝z + z + 29, 0⎠
In [19]: r.poly
Out[19]: PurePoly(z**3 + z + 29, z, domain='ZZ')
In [20]: r.poly.gen
Out[20]: z
In [22]: (r.poly.gen**3).as_poly().rem(r.poly)
Out[22]: Poly(-z - 29, z, domain='ZZ')
In [26]: (r.poly.gen**3).as_poly(domain=QQ).rem(r.poly)(r)
Out[26]:
⎛ 3 ⎞
29 CRootOf⎝2⋅z + z + 29, 0⎠
- ── - ─────────────────────────
2 2
I think there was previous discussion that maybe this simplification should not happen automatically. When you look at something like gh-26816 it seems obvious that the simplification is desired. The case whee it may not be desired is just where a simple power expands to a larger polynomial. On balance I think that this should simplify automatically just like integer powers of rational powers do. |
@oscarbenjamin @sylee957 |
We have set a condition that prevents expansion when def invert(self, g=None, *gens, **args):
if g is not None:
raise NotImplementedError()
try:
inv = invert(self.poly.gen, self.poly)
except NotInvertible:
raise NotImplementedError()
return inv.subs(self.poly.gen, self) |
if 0 < other: | ||
x = self.poly.gen | ||
elif other < 0: | ||
other = -other | ||
try: | ||
x = invert(self.poly.gen, self.poly) | ||
except NotInvertible: | ||
return None | ||
return (x**other).as_poly(domain=QQ).rem(self.poly)(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rewrite this as
x = Poly([0, 1], self.poly.gen, domain=QQ)
n = other.p
if n > 0:
p = (x ** n).rem(self.poly)
else:
p = (x ** -n).invert(self.poly)
return p(self)
There are a few ways that Poly
should make this easier like firstly there should be something analogous to Poly.gen
but to get the result as a Poly. Secondly Poly should have a method like powmod
for computing (a ** n) % b
that can also handle negative powers (maybe even pow(a, n, b)
should do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using x = Poly({abs(n): 1}, self.poly.gen, domain=QQ)
might also be fine, but is this not recommended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that works. The main point is to avoid converting between Poly and Expr as much as possible and also to avoid making the Poly code guess the generators and domain.
That seems reasonable to me. Also it would be good to have something that can rationalise the denominator to bring all RootOf into the numerator. |
I'm not sure about if it better not to expand that. I think that the only concern is that the number of terms can increase, for example, However, that is none issue because that should not be different than expanding to any positive power, let's say, |
I don't have any strong evidence either. |
Other kinds of division such as It would be worth checking how other systems handle their equivalents of RootOf. |
The inverse of |
Isn't that just what
No, I don't think it should be handled in Here is how it works with surds: In [17]: cbrt(2)
Out[17]:
3 ___
╲╱ 2
In [18]: cbrt(2)**2
Out[18]:
2/3
2
In [19]: cbrt(2)**3
Out[19]: 2
In [20]: cbrt(2)**-1
Out[20]:
2/3
2
────
2
In [21]: 1/(sqrt(2) + 1)
Out[21]:
1
──────
1 + √2
In [22]: radsimp(1/(sqrt(2) + 1))
Out[22]: -1 + √2 RootOf of degree 2 expands to surds so we should perhaps want manipulation of RootOf and manipulation of surds to be similar to some extent. |
How about organizing it so that I might not be following the conversation correctly. Apologies if I’m saying something irrelevant. |
Yes, but there is the question about what to do when In [1]: p = x**5 - x + 1
In [2]: r = RootOf(p, 0)
In [3]: r
Out[3]:
⎛ 5 ⎞
CRootOf⎝x - x + 1, 0⎠ For r**5 -> r - 1
r**100 -> 627401*r**4 - 735723*r**3 + 864339*r**2 - 1006897*r + 540536 This is analogous to what happens with surds e.g.: In [12]: s = 3**(S(1)/5)
In [13]: s
Out[13]:
5 ___
╲╱ 3
In [14]: s**5
Out[14]: 3
In [15]: s**100
Out[15]: 3486784401 For negative r**-5 -> 1/r**5 -> 1/(r - 1) We can instead though invert r**-5 -> -r**4 - r**3 - r**2 - r This means that we do not introduce an The other negative
This is like what happens with surds except in the surd case there is no Add e.g.: In [21]: s
Out[21]:
5 ___
╲╱ 3
In [22]: 1/s
Out[22]:
4/5
3
────
3 The question now is of these should be chosen for automatic simplification. If we working in the algebraic field In [26]: a = AlgebraicNumber(r, alias='alpha')
In [27]: K = QQ.algebraic_field(a)
In [28]: aa = K.from_sympy(a)
In [29]: K.to_sympy(1 + aa/(1 - aa))
Out[29]:
3 2 4
α + α + α + α I think that for Expr we don't expect this canonical form if there is an Add in the denominator unless some function is used to rationalise the denominator. We could make it so that all integer powers of |
I think we should investigate how other computer algebra systems like Mathematica, Maple, etc handle their versions of RootOf. |
Thank you for your explanation. This is a difficult issue, and I believe it won't be resolved overnight (which is why there have been so many discussions on it so far). I think I lack the ability to conduct further research or reach a conclusion here, so it might be best to close this PR. |
Just want to point out that, when it comes time to reduce I don't know if it would be faster or not. Presumably the reason the linear recurrence approach is ever used is that it can be faster, but whether it actually is in this case might depend on implementation details. |
In python-flint some poly types have a In [14]: x = flint.nmod_poly([0, 1], 11)
In [15]: p = x**5 + 2
In [16]: %time (1 + x).pow_mod(100000000000, p)
CPU times: user 98 μs, sys: 10 μs, total: 108 μs
Wall time: 282 μs
Out[16]: 4*x^4 + 6*x^3 + 8*x^2 + 4*x + 5 We don't use this yet in SymPy but it could be used in In the latest prerelease of python-flint ( In [31]: import flint.types._gr as gr
In [32]: x = flint.fmpq_poly([0, 1])
In [33]: p = x**5 + 2
In [34]: K = gr.gr_nf_ctx.new(p)
In [35]: K
Out[35]: gr_nf_ctx(x^5 + 2)
In [36]: q = 1 + K.gen()
In [37]: q
Out[37]: a+1
In [38]: q**2
Out[38]: a^2 + 2*a + 1
In [39]: %time q**100
CPU times: user 79 μs, sys: 8 μs, total: 87 μs
Wall time: 109 μs
Out[39]: 2482843830166169701737607986025*a^4 + 2762435876506655586044308684300*a^3 + 1858222353424857283143655476550*a^2 - 191302639563165531348043321100*a - 2807500490603043882196052199999 In principle we can make computing powers of RootOf very fast. I suspect though that actually computing the power will likely not be the dominant cost. What matters more is the downstream impact on other symbolic manipulation routines that need to manipulate the expressions after reduction. Consider something like: In [3]: rs = all_roots(Poly([1, 2, 3, 4, 5, 6], x))
In [4]: rs
Out[4]:
⎡ ⎛ 5 4 3 2 ⎞ ⎛ 5 4 3 2 ⎞ ↪
⎣CRootOf⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 0⎠, CRootOf⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 1⎠, CRoo ↪
↪ ⎛ 5 4 3 2 ⎞ ⎛ 5 4 3 2 ⎞ ↪
↪ tOf⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 2⎠, CRootOf⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 3⎠, CRootOf ↪
↪ ⎛ 5 4 3 2 ⎞⎤
↪ ⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 4⎠⎦
In [5]: prod(rs)**5
Out[5]:
5 5 ↪
⎛ 5 4 3 2 ⎞ ⎛ 5 4 3 2 ⎞ ↪
CRootOf⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 0⎠ ⋅CRootOf⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 1⎠ ⋅CRoot ↪
↪ 5 5 ↪
↪ ⎛ 5 4 3 2 ⎞ ⎛ 5 4 3 2 ⎞ ⎛ ↪
↪ Of⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 2⎠ ⋅CRootOf⎝x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 3⎠ ⋅CRootOf⎝ ↪
↪ 5
↪ 5 4 3 2 ⎞
↪ x + 2⋅x + 3⋅x + 4⋅x + 5⋅x + 6, 4⎠ Now reduce each of these powers and the expression sort of explodes. Is that a likely problem? Perhaps not but the downstream manipulation of the expressions is likely a bigger performance concern than the reduction itself. Also on performance python-flint has routines for isolating polynomial roots that are much faster than those in SymPy. Using those would make the construction of RootOf much faster in the first place. |
I have implemented the power function for$x^3 + x + 1 = 0$ . Since $x^3 = -x - 1$ ,
CRootOf
. For example,r = CRootOf(x**3 + x + 1, 0)
is one of the roots of the equationr**3
can be calculated as-r - 1
. Similarly, the powers ofCRootOf
can be transformed into a polynomial of degree less than the original polynomial.References to other Issues or PRs
Brief description of what is fixed or changed
Other comments
Release Notes
CRootOf
.