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

ENH, WIP: Add 2-D fitting function for polynomials #14151

Open
wants to merge 20 commits into
base: main
Choose a base branch
from

Conversation

AWehrhahn
Copy link

ENH: add 2d polynomial fit function to numpy.polynomial.

@charris charris changed the title Polyfit2d ENH, WIP: Add 2-D fitting function for polynomials Jul 29, 2019
@charris
Copy link
Member

charris commented Jul 29, 2019

New functions need to be discussed on the mailing list.

In practice, I've found it useful to have a way to limit which coefficients are allowed to vary. The case where deg(x) + deg(y) <= N for some N turns up fairly often. We also like to keep functions common between all the polynomial types when possible, look at how the current 1-D fitting is implemented.

The style changes should be in a separate PR and aren't required by the current NumPy standard, spaces around * and / aren't needed.

@charris
Copy link
Member

charris commented Jul 29, 2019

If you are feeling more ambitious, it would be nice to design a 2-D class to complement the current 1-class. It would have partial derivative methods, maybe integration too. It will not be a trivial effort, I believe SciPy (@rgommers) is also looking into multidimensional methods for interpolation and this might be related.

@AWehrhahn
Copy link
Author

Where can I find the mailing list, I just followed the guide and it didn't mention anything.

I included a case for deg(x) + deg(y) <= N with the max_degree parameter.
What do you mean with keeping functions common between polynomial types? polyfit1d is implemented more or less the same way?

The style changes where unintentional, that was just the auto-formatter doing its work. Will reverse them in the next commit.

I don't think I will have time to do a proper 2d polynomial class, maybe at some later time

@charris
Copy link
Member

charris commented Jul 29, 2019

What do you mean with keeping functions common between polynomial types?

That all six polynomial types have the same basic set of functions. See numpy/polynomial/polyutils.py for an example of abstracting the common fit functionality in the _fit function. That function is then used to implement 1-D fitting for all the polynomial types.

The mailing list is numpy-discussion@python.org, but if you initiate a discussion it is best to subscribe at https://mail.python.org/mailman/listinfo/numpy-discussion.

@grlee77
Copy link
Contributor

grlee77 commented Jul 29, 2019

I am not an expert in this area, but have wanted to do this previously (but in 3D rather than 2D) and some searching turned up scikit-learn as an existing solution. It is possible to fit n-dimensional polynomials using scikit-learn's PolynomialFeatures combined with a linear model like LinearRegression or Ridge. The examples given in the docs are for 1D, but the underlying PolynomialFeatures can take multiple coordinates for 2D or higher dimensional data.

A benefit of the API of polyfit2d proposed here, is that it should be easier to quickly understand for a user just wanting to do 2D interpolation without the overhead of having to learn about scikit-learn classes / pipelines.

@AWehrhahn
Copy link
Author

I moved the 2d fit procedure into polyutils and added functions in the other polynomial classes for the 2d fit. I had to disable the scaling though as the shift works differently (I assume).

If given the maximum combined degree of the coefficients is limited
to this value, i.e. all terms with `n` + `m` > max_degree are set to 0.
The default is None.
scale : bool, optional
Copy link
Member

Choose a reason for hiding this comment

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

Scaling is even more important for the other polynomial basis whose weights have compact support, which is why I'd like to see a class for 2-d objects at some point so that the extra information, also including the selection of coefficients, could be carried along. I don't think it belongs in the base fitting function, however. It is also possible to scale the 2d domain to [-1, 1] in each direction and get good results in most cases, images and such.

@charris
Copy link
Member

charris commented Aug 16, 2019

Wow, nice job, just a couple of comments.

  • Code lines need to be < 80 characters in length, a few exceed that.
  • Docstring lines should be <= 75 characters in length.
  • There are a couple of typos, it would be good to run a spellcheck on everything.
  • Scale is only present in the power basis, and should probably be omitted on that count.

I'd really like to figure out a way to reduce the amount of repetition in the docstrings, but haven't solved that problem yet. The polynomial documentation is a significant part of the total NumPy documentation :) There is an argument to be made for the polynomials to be their own project.

If you have any ideas of how to make a 2d class, I'd like to hear it. Such a class would include partial derivatives, along with scaling and offsets.

I'm wondering if it would make sense to use a mask for selecting the fitted coefficients or would that be too general? The current setup covers the most common options.

@charris
Copy link
Member

charris commented Aug 16, 2019

I still need to review the code in detail, the tests in particular.

@eric-wieser
Copy link
Member

I have some ideas on ND versions, possibly with mixed polynomial types - will try to dump them in a hackmd document or issue at some point.

@eric-wieser eric-wieser self-requested a review August 16, 2019 22:27
@AWehrhahn
Copy link
Author

Adding a mask for the selection of the fitted polynomial degrees would be pretty simple, considering that is what i do for max_degree anyway.

--------

"""
return pu._fit2d(chebvander2d, x, y, z, deg, rcond, full, w, max_degree, False)
Copy link
Member

Choose a reason for hiding this comment

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

Tempting to pass chebvander in directly here, and let pu._fit2d call pu._vander2d

Copy link
Author

Choose a reason for hiding this comment

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

I mean that is exactly what chebvander2d is doing. No need to repeat that here I think.

Copy link
Member

Choose a reason for hiding this comment

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

The reason I suggest this is that you if you passed chebvander directly onto _vander_nd, then that would allow fitting of mixed-axis polynomicals, eg pu._fit2d([chebvander, polyvander], ...)

Copy link
Author

Choose a reason for hiding this comment

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

How about:

if not callable(vander2d_f):
    vander2d_f = lambda x, y, deg: _vander_nd_flat(vander2d_f, (x, y), deg)

I.e. allow both?

@AWehrhahn
Copy link
Author

I added the degree handling, using a mask. One can then pass a 2d array with the same size as the output coefficient matrix, where degrees that are to be used are set to 1 (or more), and the others set to 0.

Comment on lines 790 to 792
for i, j in idx:
coeff[i, j] /= scale_x ** i * scale_y ** j
return coeff
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 this can be vectorized, will come back to it...

Copy link
Author

Choose a reason for hiding this comment

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

Done

@@ -1647,6 +1647,129 @@ def chebfit(x, y, deg, rcond=None, full=False, w=None):
return pu._fit(chebvander, x, y, deg, rcond, full, w)


def chebfit2d(x, y, z, deg, rcond=None, full=False, w=None, max_degree=None):
"""
2d Least squares fit of Chebyshev series to data.
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should construct this docstring from a template, it irritates me to see such a large docstring for such a small function body.

@eric-wieser eric-wieser added this to the 1.19.0 release milestone Oct 28, 2019
Copy link
Author

@AWehrhahn AWehrhahn left a comment

Choose a reason for hiding this comment

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

I fixed this in a later version

numpy/polynomial/polyutils.py Outdated Show resolved Hide resolved
@@ -725,6 +726,182 @@ def _fit(vander_f, x, y, deg, rcond=None, full=False, w=None):
else:
return c

def _fitnd(vandernd_f, coords, data, deg=1, rcond=None, full=False, w=None,
Copy link
Member

Choose a reason for hiding this comment

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

w is not documented

Comment on lines 737 to 739
vander2d_f : {function(array_like, ..., int) -> ndarray, list of function(array_like, int) -> ndarray}
The 2d vander function, such as ``polyvander2d``,
or a list of 1d vander functions for each dimension
Copy link
Member

Choose a reason for hiding this comment

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

Can we remove the complexity here and only allow the list of 1d vander functions?

Copy link
Author

Choose a reason for hiding this comment

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

Sure, will change that soon

@@ -1663,6 +1663,130 @@ def chebfit(x, y, deg, rcond=None, full=False, w=None):
return pu._fit(chebvander, x, y, deg, rcond, full, w)


def chebfit2d(x, y, z, deg, rcond=None, full=False, w=None, max_degree=None):
Copy link
Member

Choose a reason for hiding this comment

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

Any reason not to immediately go to nd?

Suggested change
def chebfit2d(x, y, z, deg, rcond=None, full=False, w=None, max_degree=None):
def chebfitnd(xs, fxs, deg, *, rcond=None, full=False, w=None, max_degree=None):

Copy link
Author

Choose a reason for hiding this comment

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

The function call is slightly different. The 2d version has seperate paramaters for each dimension x and y. While the nd version only has 1 parameter (x, y) with all coordinates in a tuple.
The 2d version is more consistent with the existing functions for e.g polyval2d.

Copy link
Member

@eric-wieser eric-wieser May 13, 2020

Choose a reason for hiding this comment

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

So, I'd argue that in hindsight the 2d functions were a mistake - we should have gone straight from 1d to nd, rather than blowing up our API with intermediate integers.

Since what you're adding is a new API, what I'm arguing is that we should learn from our mistake, and go straight to writing the nd function (and the slight argument difference this entails).

Copy link
Member

@eric-wieser eric-wieser May 13, 2020

Choose a reason for hiding this comment

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

If you feel strongly about the calling convention, you could use instead:

Suggested change
def chebfit2d(x, y, z, deg, rcond=None, full=False, w=None, max_degree=None):
def chebfitnd(*args, *, deg, rcond=None, full=False, w=None, max_degree=None):
*xs, fxs = args

Copy link
Author

Choose a reason for hiding this comment

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

I have no particular attachement to the calling convention

Copy link
Contributor

Choose a reason for hiding this comment

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

Why not deprecating all the XXX2d and XXX3d functions (like chebfit2d, chebfit3d, chebval2d, chebval3d, chebgrid2d, chebgrid3d, chebvander2d, chebvander3d, etc.) since they all are obsolete once you have the XXXnd functions (like chebfitnd, chebvalnd, chebgridnd, chebvandernd, ...)?

--------

"""
return pu._fitnd(chebvander2d, (x, y), z, deg, rcond, full, w, max_degree)
Copy link
Member

Choose a reason for hiding this comment

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

With my above suggestion,

Suggested change
return pu._fitnd(chebvander2d, (x, y), z, deg, rcond, full, w, max_degree)
return pu._fitnd([chebvander] * len(xs), xs, fxs, deg, rcond, full, w, max_degree)

Base automatically changed from master to main March 4, 2021 02:04
@mattip mattip modified the milestones: 1.21.0 release, 1.22.0 release May 5, 2021
@rgommers rgommers removed this from the 1.22.0 release milestone Nov 12, 2021
pbrod added a commit to pbrod/numpy that referenced this pull request Dec 7, 2021
Currently there are no functions in chebyshev.py that do N-dimensional fitting for N>1
This submission is a attempt to fill the above mentioned gap and complements the efforts in numpy#14151.

The multidimensional interpolation method implemented here uses the 1D Discrete Cosine Transform (DCT) instead of the vandermonde method. This is a reimplementation of the chebfitfunc function found in numpy#6071. The accuracy of the new interpolation method is better than the old one especially for polynomials of high degrees.

Also added domain as input to chebinterpolate in order to interpolate the function over any input domain.
Added test_2d_approximation for testing interpolation of a 2D function.
Replaced all references to the missing chebfromfunction with chebinterpolate.
pbrod added a commit to pbrod/numpy that referenced this pull request Dec 7, 2021
Currently there are no functions in chebyshev.py that do N-dimensional fitting for N>1
This submission is a attempt to fill the above mentioned gap and complements the efforts in numpy#14151.

The multidimensional interpolation method implemented here uses the 1D Discrete Cosine Transform (DCT) instead of the vandermonde method. This is a reimplementation of the chebfitfunc function found in numpy#6071. The accuracy of the new interpolation method is better than the old one especially for polynomials of high degrees.

Also added domain as input to chebinterpolate in order to interpolate the function over any input domain.
Added test_2d_approximation for testing interpolation of a 2D function.
Replaced all references to the missing chebfromfunction with chebinterpolate.
pbrod added a commit to pbrod/numpy that referenced this pull request Feb 14, 2022
Currently there are no functions in chebyshev.py that do N-dimensional fitting for N>1
This submission is a attempt to fill the above mentioned gap and complements the efforts in numpy#14151.

The multidimensional interpolation method implemented here uses the 1D Discrete Cosine Transform (DCT) instead of the vandermonde method. This is a reimplementation of the chebfitfunc function found in numpy#6071. The accuracy of the new interpolation method is better than the old one especially for polynomials of high degrees.

Also added domain as input to chebinterpolate in order to interpolate the function over any input domain.
Added test_2d_approximation for testing interpolation of a 2D function.
Replaced all references to the missing chebfromfunction with chebinterpolate.
pbrod added a commit to pbrod/numpy that referenced this pull request Feb 14, 2022
Currently there are no functions in chebyshev.py that do N-dimensional fitting for N>1
This submission is a attempt to fill the above mentioned gap and complements the efforts in numpy#14151.

The multidimensional interpolation method implemented here uses the 1D Discrete Cosine Transform (DCT) instead of the vandermonde method. This is a reimplementation of the chebfitfunc function found in numpy#6071. The accuracy of the new interpolation method is better than the old one especially for polynomials of high degrees.

Also added domain as input to chebinterpolate in order to interpolate the function over any input domain.
Added test_2d_approximation for testing interpolation of a 2D function.
Replaced all references to the missing chebfromfunction with chebinterpolate.
Made doctest of chebinterpolate more robust.
pbrod added a commit to pbrod/numpy that referenced this pull request Feb 14, 2022
Currently there are no functions in chebyshev.py that do N-dimensional fitting for N>1
This submission is a attempt to fill the above mentioned gap and complements the efforts in numpy#14151.

The multidimensional interpolation method implemented here uses the 1D Discrete Cosine Transform (DCT) instead of the vandermonde method. This is a reimplementation of the chebfitfunc function found in numpy#6071. The accuracy of the new interpolation method is better than the old one especially for polynomials of high degrees.

Also added domain as input to chebinterpolate in order to interpolate the function over any input domain.
Added test_2d_approximation for testing interpolation of a 2D function.
Replaced all references to the missing chebfromfunction with chebinterpolate.
Made doctest of chebinterpolate more robust.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Needs decision
Development

Successfully merging this pull request may close these issues.

7 participants