diff options
-rw-r--r-- | doc/release/1.5.0-notes.rst (renamed from doc/release/2.0.0-notes.rst) | 32 | ||||
-rw-r--r-- | numpy/polynomial/chebyshev.py | 45 | ||||
-rw-r--r-- | numpy/polynomial/polynomial.py | 42 | ||||
-rw-r--r-- | numpy/polynomial/polytemplate.py | 106 | ||||
-rw-r--r-- | numpy/polynomial/tests/test_chebyshev.py | 47 | ||||
-rw-r--r-- | numpy/polynomial/tests/test_polynomial.py | 47 |
6 files changed, 229 insertions, 90 deletions
diff --git a/doc/release/2.0.0-notes.rst b/doc/release/1.5.0-notes.rst index 68314c488..abccfdd2e 100644 --- a/doc/release/2.0.0-notes.rst +++ b/doc/release/1.5.0-notes.rst @@ -1,5 +1,5 @@ ========================= -NumPy 2.0.0 Release Notes +NumPy 1.5.0 Release Notes ========================= @@ -73,30 +73,32 @@ Changes polynomial.polynomial --------------------- -* The polyint and polyder functions now check that the specified number integrations or - derivations is a non-negative integer. The number 0 is a valid value for both - functions. +* The polyint and polyder functions now check that the specified number + integrations or derivations is a non-negative integer. The number 0 is + a valid value for both functions. * A degree method has been added to the Polynomial class. -* A cutdeg method has been added to the Polynomial class. It operates like +* A trimdeg method has been added to the Polynomial class. It operates like truncate except that the argument is the desired degree of the result, not the number of coefficients. -* The fit class function of the Polynomial class now uses None as the default - domain for the fit. The default Polynomial domain can be specified by using - [] as the domain value. +* Polynomial.fit now uses None as the default domain for the fit. The default + Polynomial domain can be specified by using [] as the domain value. +* Weights can be used in both polyfit and Polynomial.fit +* A linspace method has been added to the Polynomial class to ease plotting. polynomial.chebyshev -------------------- -* The chebint and chebder functions now check that the specified number integrations or - derivations is a non-negative integer. The number 0 is a valid value for both - functions. +* The chebint and chebder functions now check that the specified number + integrations or derivations is a non-negative integer. The number 0 is + a valid value for both functions. * A degree method has been added to the Chebyshev class. -* A cutdeg method has been added to the Chebyshev class. It operates like +* A trimdeg method has been added to the Chebyshev class. It operates like truncate except that the argument is the desired degree of the result, not the number of coefficients. -* The fit class function of the Chebyshev class now uses None as the default - domain for the fit. The default Chebyshev domain can be specified by using - [] as the domain value. +* Chebyshev.fit now uses None as the default domain for the fit. The default + Chebyshev domain can be specified by using [] as the domain value. +* Weights can be used in both chebfit and Chebyshev.fit +* A linspace method has been added to the Chebyshev class to ease plotting. histogram --------- diff --git a/numpy/polynomial/chebyshev.py b/numpy/polynomial/chebyshev.py index 2b6d7b4bb..99edecca1 100644 --- a/numpy/polynomial/chebyshev.py +++ b/numpy/polynomial/chebyshev.py @@ -1046,7 +1046,8 @@ def chebvander(x, deg) : Parameters ---------- x : array_like - Array of points. The values are converted to double or complex doubles. + Array of points. The values are converted to double or complex + doubles. deg : integer Degree of the resulting matrix. @@ -1059,15 +1060,15 @@ def chebvander(x, deg) : """ x = np.asarray(x) + 0.0 order = int(deg) + 1 - v = np.ones(x.shape + (order,), dtype=x.dtype) + v = np.ones((order,) + x.shape, dtype=x.dtype) if order > 1 : x2 = 2*x - v[...,1] = x + v[1] = x for i in range(2, order) : - v[...,i] = x2*v[...,i-1] - v[...,i-2] - return v + v[i] = v[i-1]*x2 - v[i-2] + return np.rollaxis(v, 0, v.ndim) -def chebfit(x, y, deg, rcond=None, full=False): +def chebfit(x, y, deg, rcond=None, full=False, w=None): """ Least squares fit of Chebyshev series to data. @@ -1094,6 +1095,12 @@ def chebfit(x, y, deg, rcond=None, full=False): Switch determining nature of return value. When it is False (the default) just the coefficients are returned, when True diagnostic information from the singular value decomposition is also returned. + w : array_like, shape (`M`,), optional + Weights. If not None, the contribution of each point + ``(x[i],y[i])`` to the fit is weighted by `w[i]`. Ideally the + weights are chosen so that the errors of the products ``w[i]*y[i]`` + all have the same variance. The default value is None. + .. versionadded:: 1.5.0 Returns ------- @@ -1176,17 +1183,33 @@ def chebfit(x, y, deg, rcond=None, full=False): raise TypeError, "expected non-empty vector for x" if y.ndim < 1 or y.ndim > 2 : raise TypeError, "expected 1D or 2D array for y" - if x.shape[0] != y.shape[0] : + if len(x) != len(y): raise TypeError, "expected x and y to have same length" + # set up the least squares matrices + lhs = chebvander(x, deg) + rhs = y + if w is not None: + w = np.asarray(w) + 0.0 + if w.ndim != 1: + raise TypeError, "expected 1D vector for w" + if len(x) != len(w): + raise TypeError, "expected x and w to have same length" + # apply weights + if rhs.ndim == 2: + lhs *= w[:, np.newaxis] + rhs *= w[:, np.newaxis] + else: + lhs *= w[:, np.newaxis] + rhs *= w + # set rcond if rcond is None : rcond = len(x)*np.finfo(x.dtype).eps - # set up the design matrix and solve the least squares equation - A = chebvander(x, deg) - scl = np.sqrt((A*A).sum(0)) - c, resids, rank, s = la.lstsq(A/scl, y, rcond) + # scale the design matrix and solve the least squares equation + scl = np.sqrt((lhs*lhs).sum(0)) + c, resids, rank, s = la.lstsq(lhs/scl, rhs, rcond) c = (c.T/scl).T # warn on rank reduction diff --git a/numpy/polynomial/polynomial.py b/numpy/polynomial/polynomial.py index 9f98ad4ca..3144d9985 100644 --- a/numpy/polynomial/polynomial.py +++ b/numpy/polynomial/polynomial.py @@ -646,14 +646,14 @@ def polyvander(x, deg) : """ x = np.asarray(x) + 0.0 order = int(deg) + 1 - v = np.ones(x.shape + (order,), dtype=x.dtype) + v = np.ones((order,) + x.shape, dtype=x.dtype) if order > 1 : - v[...,1] = x + v[1] = x for i in range(2, order) : - v[...,i] = x*v[...,i-1] - return v + v[i] = v[i-1]*x + return np.rollaxis(v, 0, v.ndim) -def polyfit(x, y, deg, rcond=None, full=False): +def polyfit(x, y, deg, rcond=None, full=False, w=None): """ Least-squares fit of a polynomial to data. @@ -684,6 +684,12 @@ def polyfit(x, y, deg, rcond=None, full=False): (the default) just the coefficients are returned; when ``True``, diagnostic information from the singular value decomposition (used to solve the fit's matrix equation) is also returned. + w : array_like, shape (`M`,), optional + Weights. If not None, the contribution of each point + ``(x[i],y[i])`` to the fit is weighted by `w[i]`. Ideally the + weights are chosen so that the errors of the products ``w[i]*y[i]`` + all have the same variance. The default value is None. + .. versionadded:: 1.5.0 Returns ------- @@ -787,17 +793,33 @@ def polyfit(x, y, deg, rcond=None, full=False): raise TypeError, "expected non-empty vector for x" if y.ndim < 1 or y.ndim > 2 : raise TypeError, "expected 1D or 2D array for y" - if x.shape[0] != y.shape[0] : + if len(x) != len(y): raise TypeError, "expected x and y to have same length" + # set up the least squares matrices + lhs = polyvander(x, deg) + rhs = y + if w is not None: + w = np.asarray(w) + 0.0 + if w.ndim != 1: + raise TypeError, "expected 1D vector for w" + if len(x) != len(w): + raise TypeError, "expected x and w to have same length" + # apply weights + if rhs.ndim == 2: + lhs *= w[:, np.newaxis] + rhs *= w[:, np.newaxis] + else: + lhs *= w[:, np.newaxis] + rhs *= w + # set rcond if rcond is None : rcond = len(x)*np.finfo(x.dtype).eps - # set up the design matrix and solve the least squares equation - A = polyvander(x, deg) - scl = np.sqrt((A*A).sum(0)) - c, resids, rank, s = la.lstsq(A/scl, y, rcond) + # scale the design matrix and solve the least squares equation + scl = np.sqrt((lhs*lhs).sum(0)) + c, resids, rank, s = la.lstsq(lhs/scl, rhs, rcond) c = (c.T/scl).T # warn on rank reduction diff --git a/numpy/polynomial/polytemplate.py b/numpy/polynomial/polytemplate.py index 1f17593dd..75b1b4eb1 100644 --- a/numpy/polynomial/polytemplate.py +++ b/numpy/polynomial/polytemplate.py @@ -322,13 +322,13 @@ class $name(pu.PolyBase) : squares where the coefficients of the high degree terms may be very small. - Parameters: - ----------- + Parameters + ---------- deg : non-negative int The series is reduced to degree `deg` by discarding the high order terms. The value of `deg` must be a non-negative integer. - Returns: + Returns ------- new_instance : $name New instance of $name with reduced degree. @@ -343,8 +343,8 @@ class $name(pu.PolyBase) : def convert(self, domain=None, kind=None) : """Convert to different class and/or domain. - Parameters: - ----------- + Parameters + ---------- domain : {None, array_like} The domain of the new series type instance. If the value is is ``None``, then the default domain of `kind` is used. @@ -353,17 +353,17 @@ class $name(pu.PolyBase) : should be converted. If kind is ``None``, then the class of the current instance is used. - Returns: - -------- + Returns + ------- new_series_instance : `kind` The returned class can be of different type than the current instance and/or have a different domain. - Examples: - --------- + Examples + -------- - Notes: - ------ + Notes + ----- Conversion between domains and class types can result in numerically ill defined series. @@ -385,8 +385,8 @@ class $name(pu.PolyBase) : separately, then the linear function must be substituted for the ``x`` in the standard representation of the base polynomials. - Returns: - -------- + Returns + ------- off, scl : floats or complex The mapping function is defined by ``off + scl*x``. @@ -411,12 +411,12 @@ class $name(pu.PolyBase) : ``[0]``. A new $name instance is returned with the new coefficients. The current instance remains unchanged. - Parameters: - ----------- + Parameters + ---------- tol : non-negative number. All trailing coefficients less than `tol` will be removed. - Returns: + Returns ------- new_instance : $name Contains the new set of coefficients. @@ -432,13 +432,13 @@ class $name(pu.PolyBase) : can be useful in least squares where the coefficients of the high degree terms may be very small. - Parameters: - ----------- + Parameters + ---------- size : positive int The series is reduced to length `size` by discarding the high degree terms. The value of `size` must be a positive integer. - Returns: + Returns ------- new_instance : $name New instance of $name with truncated coefficients. @@ -458,8 +458,8 @@ class $name(pu.PolyBase) : A new instance of $name is returned that has the same coefficients and domain as the current instance. - Returns: - -------- + Returns + ------- new_instance : $name New instance of $name with the same coefficients and domain. @@ -472,8 +472,8 @@ class $name(pu.PolyBase) : Return an instance of $name that is the definite integral of the current series. Refer to `${nick}int` for full documentation. - Parameters: - ----------- + Parameters + ---------- m : non-negative int The number of integrations to perform. k : array_like @@ -484,8 +484,8 @@ class $name(pu.PolyBase) : lbnd : Scalar The lower bound of the definite integral. - Returns: - -------- + Returns + ------- integral : $name The integral of the series using the same domain. @@ -509,13 +509,13 @@ class $name(pu.PolyBase) : Return an instance of $name that is the derivative of the current series. Refer to `${nick}der` for full documentation. - Parameters: - ----------- + Parameters + ---------- m : non-negative int The number of integrations to perform. - Returns: - -------- + Returns + ------- derivative : $name The derivative of the series using the same domain. @@ -545,8 +545,35 @@ class $name(pu.PolyBase) : roots = ${nick}roots(self.coef) return pu.mapdomain(roots, $domain, self.domain) + def linspace(self, n): + """Return x,y values at equally spaced points in domain. + + Returns x, y values at `n` equally spaced points across domain. + Here y is the value of the polynomial at the points x. This is + intended as a plotting aid. + + Paramters + --------- + n : int + Number of point pairs to return. + + Returns + ------- + x, y : ndarrays + ``x`` is equal to linspace(self.domain[0], self.domain[1], n) + ``y`` is the polynomial evaluated at ``x``. + + .. versionadded:: 1.5.0 + + """ + x = np.linspace(self.domain[0], self.domain[1], n) + y = self(x) + return x, y + + + @staticmethod - def fit(x, y, deg, domain=None, rcond=None, full=False) : + def fit(x, y, deg, domain=None, rcond=None, full=False, w=None) : """Least squares fit to data. Return a `$name` instance that is the least squares fit to the data @@ -565,12 +592,12 @@ class $name(pu.PolyBase) : passing in a 2D-array that contains one dataset per column. deg : int Degree of the fitting polynomial - domain : {None, [], [beg, end]}, optional + domain : {None, [beg, end], []}, optional Domain to use for the returned $name instance. If ``None``, - then a minimal domain that covers the points `x` is chosen. - If ``[]`` the default domain ``$domain`` is used. The - default value is $domain in numpy 1.4.x and ``None`` in - numpy 2.0.0. The keyword value ``[]`` was added in numpy 2.0.0. + then a minimal domain that covers the points `x` is chosen. If + ``[]`` the default domain ``$domain`` is used. The default + value is $domain in numpy 1.4.x and ``None`` in later versions. + The ``'[]`` value was added in numpy 1.5.0. rcond : float, optional Relative condition number of the fit. Singular values smaller than this relative to the largest singular value will be @@ -582,6 +609,13 @@ class $name(pu.PolyBase) : (the default) just the coefficients are returned, when True diagnostic information from the singular value decomposition is also returned. + w : array_like, shape (M,), optional + Weights. If not None the contribution of each point + ``(x[i],y[i])`` to the fit is weighted by `w[i]`. Ideally the + weights are chosen so that the errors of the products + ``w[i]*y[i]`` all have the same variance. The default value is + None. + .. versionadded:: 1.5.0 Returns ------- @@ -605,7 +639,7 @@ class $name(pu.PolyBase) : elif domain == [] : domain = $domain xnew = pu.mapdomain(x, domain, $domain) - res = ${nick}fit(xnew, y, deg, rcond=None, full=full) + res = ${nick}fit(xnew, y, deg, w=w, rcond=rcond, full=full) if full : [coef, status] = res return $name(coef, domain=domain), status diff --git a/numpy/polynomial/tests/test_chebyshev.py b/numpy/polynomial/tests/test_chebyshev.py index a893921ce..981481ff1 100644 --- a/numpy/polynomial/tests/test_chebyshev.py +++ b/numpy/polynomial/tests/test_chebyshev.py @@ -283,18 +283,33 @@ class TestMisc(TestCase) : assert_raises(TypeError, ch.chebfit, [1], [[[1]]], 0) assert_raises(TypeError, ch.chebfit, [1, 2], [1], 0) assert_raises(TypeError, ch.chebfit, [1], [1, 2], 0) + assert_raises(TypeError, ch.chebfit, [1], [1], 0, w=[[1]]) + assert_raises(TypeError, ch.chebfit, [1], [1], 0, w=[1,1]) # Test fit x = np.linspace(0,2) y = f(x) - coef = ch.chebfit(x, y, 3) - assert_equal(len(coef), 4) - assert_almost_equal(ch.chebval(x, coef), y) - coef = ch.chebfit(x, y, 4) - assert_equal(len(coef), 5) - assert_almost_equal(ch.chebval(x, coef), y) - coef2d = ch.chebfit(x, np.array([y,y]).T, 4) - assert_almost_equal(coef2d, np.array([coef,coef]).T) + # + coef3 = ch.chebfit(x, y, 3) + assert_equal(len(coef3), 4) + assert_almost_equal(ch.chebval(x, coef3), y) + # + coef4 = ch.chebfit(x, y, 4) + assert_equal(len(coef4), 5) + assert_almost_equal(ch.chebval(x, coef4), y) + # + coef2d = ch.chebfit(x, np.array([y,y]).T, 3) + assert_almost_equal(coef2d, np.array([coef3,coef3]).T) + # test weighting + w = np.zeros_like(x) + yw = y.copy() + w[1::2] = 1 + y[0::2] = 0 + wcoef3 = ch.chebfit(x, yw, 3, w=w) + assert_almost_equal(wcoef3, coef3) + # + wcoef2d = ch.chebfit(x, np.array([yw,yw]).T, 3, w=w) + assert_almost_equal(wcoef2d, np.array([coef3,coef3]).T) def test_chebtrim(self) : coef = [2, -1, 1, 0] @@ -402,7 +417,7 @@ class TestChebyshevClass(TestCase) : def test_degree(self) : assert_equal(self.p1.degree(), 2) - def test_cutdeg(self) : + def test_trimdeg(self) : assert_raises(ValueError, self.p1.cutdeg, .5) assert_raises(ValueError, self.p1.cutdeg, -1) assert_equal(len(self.p1.cutdeg(3)), 3) @@ -459,6 +474,13 @@ class TestChebyshevClass(TestCase) : tgt = [0, .5, 1] assert_almost_equal(res, tgt) + def test_linspace(self): + xdes = np.linspace(0, 1, 20) + ydes = self.p2(xdes) + xres, yres = self.p2.linspace(20) + assert_almost_equal(xres, xdes) + assert_almost_equal(yres, ydes) + def test_fromroots(self) : roots = [0, .5, 1] p = ch.Chebyshev.fromroots(roots, domain=[0, 1]) @@ -483,6 +505,13 @@ class TestChebyshevClass(TestCase) : p = ch.Chebyshev.fit(x, y, 3, []) assert_almost_equal(p(x), y) assert_almost_equal(p.domain, [-1, 1]) + # test that fit accepts weights. + w = np.zeros_like(x) + yw = y.copy() + w[1::2] = 1 + yw[0::2] = 0 + p = ch.Chebyshev.fit(x, yw, 3, w=w) + assert_almost_equal(p(x), y) def test_identity(self) : x = np.linspace(0,3) diff --git a/numpy/polynomial/tests/test_polynomial.py b/numpy/polynomial/tests/test_polynomial.py index 1b8c0f8a5..4bfbc46d9 100644 --- a/numpy/polynomial/tests/test_polynomial.py +++ b/numpy/polynomial/tests/test_polynomial.py @@ -263,18 +263,33 @@ class TestMisc(TestCase) : assert_raises(TypeError, poly.polyfit, [1], [[[1]]], 0) assert_raises(TypeError, poly.polyfit, [1, 2], [1], 0) assert_raises(TypeError, poly.polyfit, [1], [1, 2], 0) + assert_raises(TypeError, poly.polyfit, [1], [1], 0, w=[[1]]) + assert_raises(TypeError, poly.polyfit, [1], [1], 0, w=[1,1]) # Test fit x = np.linspace(0,2) y = f(x) - coef = poly.polyfit(x, y, 3) - assert_equal(len(coef), 4) - assert_almost_equal(poly.polyval(x, coef), y) - coef = poly.polyfit(x, y, 4) - assert_equal(len(coef), 5) - assert_almost_equal(poly.polyval(x, coef), y) - coef2d = poly.polyfit(x, np.array([y,y]).T, 4) - assert_almost_equal(coef2d, np.array([coef,coef]).T) + # + coef3 = poly.polyfit(x, y, 3) + assert_equal(len(coef3), 4) + assert_almost_equal(poly.polyval(x, coef3), y) + # + coef4 = poly.polyfit(x, y, 4) + assert_equal(len(coef4), 5) + assert_almost_equal(poly.polyval(x, coef4), y) + # + coef2d = poly.polyfit(x, np.array([y,y]).T, 3) + assert_almost_equal(coef2d, np.array([coef3,coef3]).T) + # test weighting + w = np.zeros_like(x) + yw = y.copy() + w[1::2] = 1 + yw[0::2] = 0 + wcoef3 = poly.polyfit(x, yw, 3, w=w) + assert_almost_equal(wcoef3, coef3) + # + wcoef2d = poly.polyfit(x, np.array([yw,yw]).T, 3, w=w) + assert_almost_equal(wcoef2d, np.array([coef3,coef3]).T) def test_polytrim(self) : coef = [2, -1, 1, 0] @@ -373,7 +388,7 @@ class TestPolynomialClass(TestCase) : def test_degree(self) : assert_equal(self.p1.degree(), 2) - def test_cutdeg(self) : + def test_trimdeg(self) : assert_raises(ValueError, self.p1.cutdeg, .5) assert_raises(ValueError, self.p1.cutdeg, -1) assert_equal(len(self.p1.cutdeg(3)), 3) @@ -430,6 +445,13 @@ class TestPolynomialClass(TestCase) : tgt = [0, .5, 1] assert_almost_equal(res, tgt) + def test_linspace(self): + xdes = np.linspace(0, 1, 20) + ydes = self.p2(xdes) + xres, yres = self.p2.linspace(20) + assert_almost_equal(xres, xdes) + assert_almost_equal(yres, ydes) + def test_fromroots(self) : roots = [0, .5, 1] p = poly.Polynomial.fromroots(roots, domain=[0, 1]) @@ -454,6 +476,13 @@ class TestPolynomialClass(TestCase) : p = poly.Polynomial.fit(x, y, 3, []) assert_almost_equal(p(x), y) assert_almost_equal(p.domain, [-1, 1]) + # test that fit accepts weights. + w = np.zeros_like(x) + yw = y.copy() + w[1::2] = 1 + yw[0::2] = 0 + p = poly.Polynomial.fit(x, yw, 3, w=w) + assert_almost_equal(p(x), y) def test_identity(self) : x = np.linspace(0,3) |