-
-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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
ENH: Use block structure in pade #14933
Conversation
This makes it much clearer what happens than using loops.
`Polynomial` is preferred according to Numpy. Furthermore, the order is more intuitive, as the Taylor coefficients are also given in accenting order.
The Padé problem is formulated and solved as [B, A] [q, p]^T = 0 The matrix A, however has a very simple block structure: A = [-I, 0]^T where `I` is the identity matrix. We partition `B` likewise B = [B_1, B_2]^T and get the Block equation [[B_1, -I], [[q], [[0], [B_2, 0]] [p]] = [0]] We can now solve the second line, which is a smaller matrix equation, for `q` and substitute it in the first line for to obtain `p`.
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.
Thanks @DerWeh! I cannot really comment the maths and will let @ev-br and others have a look 😃 (still tests seem good), still I have a suggestion for the tests and a general remark.
If I read this PR correctly, it will introduce a backward incompatibility. I am not sure how critical this function is, but we usually wait 2 releases before such changes. So for now, we could add a deprecation warning (we have a decorator for that @_deprecated
) to warn future changes.
def test_pade_trivial(): | ||
nump, denomp = pade([1.0], 0) | ||
assert_array_equal(nump.c, [1.0]) | ||
assert_array_equal(denomp.c, [1.0]) | ||
assert_array_equal(nump.coef, [1.0]) |
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.
Since you touch the whole file, can you change all assert_array_equal,assert_approx_equal
to assert_allclose
since it's also recommended to do the change?
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.
Sure, I will change it.
But are you sure about assert_array_equal
? This is a more strict test, equal, not just close. Also, I cannot find any mention that it is not recommended anymore.
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.
Oups yes you are right I thought about another one.
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.
For reference the list of non recommended functions is https://numpy.org/doc/stable/reference/routines.testing.html#asserts-not-recommended
You are right @tupui, somehow I missed that returning Should I just revert the change, or would it be smarter to add an optional argument allowing to choose between |
No worries 😃 I would not add a parameter. Here we can just have a deprecation cycle. Let's wait for other maintainer's opinion before you do any extra work. |
As we are still waiting for someone to comment on the math, I shipped my own version in the meantime as I need it. It is documented here: https://gftools.readthedocs.io/en/0.11.0/generated/gftool.hermpade.pade.html Further changes:
I also noted, that the example in the documentation is quite bad. It shows a single point, where it's hard to see the difference between Pade and Taylor expansion (you have to know 4 digits of |
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.
Looks nice! I'm not a power user of this, so am ready to trust your judgement. The list of planned improvements looks nice, these would be very welcome.
The main sticky point of this PR seems to be np.poly1d
vs np.Polynomial
. Can't just replace one with the other, so maybe it's time to cook up a replacement function and ditch the current pade
.
@@ -29,8 +32,7 @@ def pade(an, m, n=None): | |||
>>> e_exp = [1.0, 1.0, 1.0/2.0, 1.0/6.0, 1.0/24.0, 1.0/120.0] | |||
>>> p, q = pade(e_exp, 2) | |||
|
|||
>>> e_exp.reverse() | |||
>>> e_poly = np.poly1d(e_exp) | |||
>>> e_poly = np.polynomial.Polynomial(e_exp) |
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.
That's a backcompat break, as Pamphille already said. Two possibilities: either revert back to poly1d
(sigh) or add a separate function to return a Polynomial with your other improvements, and deprecate the older one.
I'd recommend the latter maybe?
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 also favor doing the latter. This further allows us to break with the convention q.coef[0]==1
and use the QR decomposition to get the null-vector instead of solving the system. This is conceptionally cleaner and more stable, albeit slower.
In rare cases, q.coef[0]==1
fails. (Had to exclude these in the property tests of my Pade implementation.)
return _Polynomial(an), _Polynomial([1]) | ||
# first solve the Toeplitz system for q | ||
# first row might contain tailing zeros | ||
top = r_[an[n+1::-1][:m+1], [0]*(m-n-1)] |
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.
Minor, trivial, optional: while we're at, how about spelling np.r_
(and removing the old-fashioned from numpy import r_
)
if m > 100: # arbitrary threshold when to use dedicated Toeplitz function | ||
p = linalg.matmul_toeplitz((an[:n+1], zeros(m+1)), q) | ||
else: | ||
p = linalg.toeplitz(an[:n+1], zeros(m+1)) @ q |
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.
About the threshold: any guidance of how to choose the threshold?
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.
No guidance, but I can simply test on my machine when matmul_toeplitz
becomes faster, assuming memory doesn't matter.
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.
Maybe include the user knob to control the switch. Or better, expose a method=
kwarg and discuss them in the docstring notes. Or maybe it's all YAGNI.
top = r_[an[n+1::-1][:m+1], [0]*(m-n-1)] | ||
an_mat = linalg.toeplitz(an[n+1:], top) | ||
# we set q[0] = 1 -> first column is -rhs | ||
q = r_[1.0, linalg.solve(an_mat[:, 1:], -an_mat[:, 0])] |
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.
Can use solve_toeplitz
instead?
EDIT: Oh, I see you mentioned this. So it's the matter of numerical stability. Maybe then add a comment, and hold on on the keyword switch until need arises.
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.
As mentioned, if we rewrite Pade I would replace solve
by a QR decomposition.
A better example would definitely be most welcome! |
Nice timing @ev-br, I just got time to work on the topic again and released an own version of Pade in my package, which I need for the Pade-Fourier algorithm. We can take https://gftools.readthedocs.io/en/0.11.0/generated/gftool.hermpade.pade.html as a reference. I used it quite a lot, but only in the Pade-Fourier context, which considers quite large polynomial degrees. With other use cases, I got little experience. If we replace As soon as we decide how to proceed, I can readily implement it. |
Great that timings overlap :-). In my own experience, I only used Pade approximants for weak-coupling expansions, where you have a handful of terms for a (hopefully convergent) diagrammatic series and try to resum it. So, not as hard-core as your uses, by quite a margin. W.r.t if this should live in interpolate or elsewhere. When originally we moved it out of |
Sure, it was just the only case where I needed it so far (and had problems with
I don't have any strong opinion where to put Pade, so we can leave it where it is. So what should I do now? Add a deprecation warning to |
yup, can
Naming is hard :-). |
Ok, closing then, and looking forward for the replacement PR @DerWeh ! |
Reference issue
None
What does this implement/fix?
The Pade is solved as the matrix quation:
The Matrix
A
, however, has the very simple block structureso the matrix problem can be written in the block structure
The new version of
pade
solves the second line of this matrix problemand substitutes the solution.
This is more stable for large degree of the denominator polynomial
q
.I personally need this, to use
pade
for the Fourier-Pade transformation with many Fourier coefficients.Additional information
Additionally, I did the following refactoring:
Polynomial
instead of oldpoly1d