• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

chebpy / chebpy / 16543631175

26 Jul 2025 08:33PM UTC coverage: 99.459%. Remained the same
16543631175

Pull #81

github

web-flow
Merge cfd4d939f into d7c9eb9ae
Pull Request #81: Introducing ruff and installing uv/uvx

12 of 13 new or added lines in 5 files covered. (92.31%)

1 existing line in 1 file now uncovered.

3678 of 3698 relevant lines covered (99.46%)

11.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.06
/chebpy/core/algorithms.py
1
import warnings
12✔
2

3
import numpy as np
12✔
4

5
from .ffts import fft, ifft
12✔
6
from .utilities import Interval, infnorm
12✔
7
from .settings import _preferences as prefs
12✔
8
from .decorators import preandpostprocess
12✔
9

10
# supress numpy division and multiply warnings
11
np.seterr(divide="ignore", invalid="ignore")
12✔
12

13
# constants
14
SPLITPOINT = -0.004849834917525
12✔
15

16

17
# local helpers
18
def find(x):
12✔
19
    return np.where(x)[0]
12✔
20

21

22
def rootsunit(ak, htol=None):
12✔
23
    """Compute the roots of a funciton on [-1,1] using the coefficeints
24
    in the associated Chebyshev series representation.
25

26
    References
27
    ----------
28
    .. [1] I. J. Good, "The colleague matrix, a Chebyshev analogue of the
29
        companion matrix", Quarterly Journal of Mathematics 12 (1961).
30

31
    .. [2] J. A. Boyd, "Computing zeros on a real interval through
32
        Chebyshev expansion and polynomial rootfinding", SIAM Journal on
33
        Numerical Analysis 40 (2002).
34

35
    .. [3] L. N. Trefethen, Approximation Theory and Approximation
36
        Practice, SIAM, 2013, chapter 18.
37
    """
38
    htol = htol if htol is not None else 1e2 * prefs.eps
12✔
39
    n = standard_chop(ak, tol=htol)
12✔
40
    ak = ak[:n]
12✔
41

42
    # if n > 50, we split and recurse
43
    if n > 50:
12✔
44
        chebpts = chebpts2(ak.size)
12✔
45
        lmap = Interval(-1, SPLITPOINT)
12✔
46
        rmap = Interval(SPLITPOINT, 1)
12✔
47
        lpts = lmap(chebpts)
12✔
48
        rpts = rmap(chebpts)
12✔
49
        lval = clenshaw(lpts, ak)
12✔
50
        rval = clenshaw(rpts, ak)
12✔
51
        lcfs = vals2coeffs2(lval)
12✔
52
        rcfs = vals2coeffs2(rval)
12✔
53
        lrts = rootsunit(lcfs, 2 * htol)
12✔
54
        rrts = rootsunit(rcfs, 2 * htol)
12✔
55
        return np.append(lmap(lrts), rmap(rrts))
12✔
56

57
    # trivial base case
58
    if n <= 1:
12✔
59
        return np.array([])
12✔
60

61
    # nontrivial base case: either compute directly or solve
62
    # a Colleague Matrix eigenvalue problem
63
    if n == 2:
12✔
64
        rts = np.array([-ak[0] / ak[1]])
12✔
65
    elif n <= 50:
12✔
66
        v = 0.5 * np.ones(n - 2)
12✔
67
        C = np.diag(v, -1) + np.diag(v, 1)
12✔
68
        C[0, 1] = 1
12✔
69
        D = np.zeros(C.shape, dtype=ak.dtype)
12✔
70
        D[-1, :] = ak[:-1]
12✔
71
        E = C - 0.5 * 1.0 / ak[-1] * D
12✔
72
        rts = np.linalg.eigvals(E)
12✔
73

74
    # discard values with large imaginary part and treat the remaining
75
    # ones as real; then sort and retain only the roots inside [-1,1]
76
    mask = abs(np.imag(rts)) < htol
12✔
77
    rts = np.real(rts[mask])
12✔
78
    rts = rts[abs(rts) <= 1.0 + htol]
12✔
79
    rts = np.sort(rts)
12✔
80
    if rts.size >= 2:
12✔
81
        rts[0] = max([rts[0], -1])
12✔
82
        rts[-1] = min([rts[-1], 1])
12✔
83
    return rts
12✔
84

85

86
@preandpostprocess
12✔
87
def bary(xx, fk, xk, vk):
12✔
88
    """Barycentric interpolation formula. See:
89

90
    J.P. Berrut, L.N. Trefethen, Barycentric Lagrange Interpolation, SIAM
91
    Review (2004)
92

93
    Inputs
94
    ------
95
    xx : numpy ndarray
96
        array of evaluation points
97
    fk : numpy ndarray
98
        array of function values at the interpolation nodes xk
99
    xk: numpy ndarray
100
        array of interpolation nodes
101
    vk: numpy ndarray
102
        barycentric weights corresponding to the interpolation nodes xk
103
    """
104

105
    # either iterate over the evaluation points, or ...
106
    if xx.size < 4 * xk.size:
12✔
107
        out = np.zeros(xx.size)
12✔
108
        for i in range(xx.size):
12✔
109
            tt = vk / (xx[i] - xk)
12✔
110
            out[i] = np.dot(tt, fk) / tt.sum()
12✔
111

112
    # ... iterate over the barycenters
113
    else:
114
        numer = np.zeros(xx.size)
12✔
115
        denom = np.zeros(xx.size)
12✔
116
        for j in range(xk.size):
12✔
117
            temp = vk[j] / (xx - xk[j])
12✔
118
            numer = numer + temp * fk[j]
12✔
119
            denom = denom + temp
12✔
120
        out = numer / denom
12✔
121

122
    # replace NaNs
123
    for k in find(np.isnan(out)):
12✔
124
        idx = find(xx[k] == xk)
12✔
125
        if idx.size > 0:
12✔
126
            out[k] = fk[idx[0]]
12✔
127

128
    return out
12✔
129

130

131
@preandpostprocess
12✔
132
def clenshaw(xx, ak):
12✔
133
    """Clenshaw's algorithm for the evaluation of a first-kind Chebyshev
134
    series expansion at some array of points x"""
135
    bk1 = 0 * xx
12✔
136
    bk2 = 0 * xx
12✔
137
    xx = 2 * xx
12✔
138
    idx = range(ak.size)
12✔
139
    for k in idx[ak.size : 1 : -2]:
12✔
140
        bk2 = ak[k] + xx * bk1 - bk2
12✔
141
        bk1 = ak[k - 1] + xx * bk2 - bk1
12✔
142
    if np.mod(ak.size - 1, 2) == 1:
12✔
143
        bk1, bk2 = ak[1] + xx * bk1 - bk2, bk1
12✔
144
    out = ak[0] + 0.5 * xx * bk1 - bk2
12✔
145
    return out
12✔
146

147

148
def standard_chop(coeffs, tol=None):
12✔
149
    """Chop a Chebyshev series to a given tolerance. This is a Python
150
    transcription of the algorithm described in:
151

152
    J. Aurentz and L.N. Trefethen, Chopping a Chebyshev series (2015)
153
    (http://arxiv.org/pdf/1512.01803v1.pdf)
154
    """
155

156
    # check magnitude of tol:
157
    tol = tol if tol is not None else prefs.eps
12✔
158
    if tol >= 1:
12✔
159
        cutoff = 1
×
160
        return cutoff
×
161

162
    # ensure length at least 17:
163
    n = coeffs.size
12✔
164
    cutoff = n
12✔
165
    if n < 17:
12✔
166
        return cutoff
12✔
167

168
    # Step 1: Convert coeffs input to a new monotonically nonincreasing
169
    # vector (envelope) normalized to begin with the value 1.
170
    b = np.flipud(np.abs(coeffs))
12✔
171
    m = np.flipud(np.maximum.accumulate(b))
12✔
172
    if m[0] == 0.0:
12✔
173
        # TODO: check this
174
        cutoff = 1  # cutoff = 0
12✔
175
        return cutoff
12✔
176
    envelope = m / m[0]
12✔
177

178
    # Step 2: Scan envelope for a value plateauPoint, the first point, if any,
179
    # that is followed by a plateau
180
    for j in np.arange(1, n):
12✔
181
        j2 = round(1.25 * j + 5)
12✔
182
        if j2 > n - 1:
12✔
183
            # there is no plateau: exit
184
            return cutoff
12✔
185
        e1 = envelope[j]
12✔
186
        e2 = envelope[int(j2)]
12✔
187
        r = 3 * (1 - np.log(e1) / np.log(tol))
12✔
188
        plateau = (e1 == 0.0) | (e2 / e1 > r)
12✔
189
        if plateau:
12✔
190
            # a plateau has been found: go to Step 3
191
            plateauPoint = j
12✔
192
            break
12✔
193

194
    # Step 3: Fix cutoff at a point where envelope, plus a linear function
195
    # included to bias the result towards the left end, is minimal.
196
    if envelope[int(plateauPoint)] == 0.0:
12✔
197
        cutoff = plateauPoint
12✔
198
    else:
199
        j3 = sum(envelope >= tol ** (7.0 / 6.0))
12✔
200
        if j3 < j2:
12✔
201
            j2 = j3 + 1
12✔
202
            envelope[j2] = tol ** (7.0 / 6.0)
12✔
203
        cc = np.log10(envelope[: int(j2)])
12✔
204
        cc = cc + np.linspace(0, (-1.0 / 3.0) * np.log10(tol), int(j2))
12✔
205
        d = np.argmin(cc)
12✔
206
        # TODO: check this
207
        cutoff = d  # + 2
12✔
208
    return min((cutoff, n - 1))
12✔
209

210

211
def adaptive(cls, fun, hscale=1, maxpow2=None):
12✔
212
    """Adaptive constructor: cycle over powers of two, calling
213
    standard_chop each time, the output of which determines whether or not
214
    we are happy."""
215
    minpow2 = 4  # 17 points
12✔
216
    maxpow2 = maxpow2 if maxpow2 is not None else prefs.maxpow2
12✔
217
    for k in range(minpow2, max(minpow2, maxpow2) + 1):
12✔
218
        n = 2**k + 1
12✔
219
        points = cls._chebpts(n)
12✔
220
        values = fun(points)
12✔
221
        coeffs = cls._vals2coeffs(values)
12✔
222
        eps = prefs.eps
12✔
223
        tol = eps * max(hscale, 1)  # scale (decrease) tolerance by hscale
12✔
224
        chplen = standard_chop(coeffs, tol=tol)
12✔
225
        if chplen < coeffs.size:
12✔
226
            coeffs = coeffs[:chplen]
12✔
227
            break
12✔
228
        if k == maxpow2:
12✔
NEW
229
            warnings.warn("The {} constructor did not converge: using {} points".format(cls.__name__, n))
×
UNCOV
230
            break
×
231
    return coeffs
12✔
232

233

234
def coeffmult(fc, gc):
12✔
235
    """Coefficient-Space multiplication of equal-length first-kind
236
    Chebyshev series."""
237
    Fc = np.append(2.0 * fc[:1], (fc[1:], fc[:0:-1]))
12✔
238
    Gc = np.append(2.0 * gc[:1], (gc[1:], gc[:0:-1]))
12✔
239
    ak = ifft(fft(Fc) * fft(Gc))
12✔
240
    ak = np.append(ak[:1], ak[1:] + ak[:0:-1]) * 0.25
12✔
241
    ak = ak[: fc.size]
12✔
242
    inputcfs = np.append(fc, gc)
12✔
243
    out = np.real(ak) if np.isreal(inputcfs).all() else ak
12✔
244
    return out
12✔
245

246

247
def barywts2(n):
12✔
248
    """Barycentric weights for Chebyshev points of 2nd kind"""
249
    if n == 0:
12✔
250
        wts = np.array([])
12✔
251
    elif n == 1:
12✔
252
        wts = np.array([1])
12✔
253
    else:
254
        wts = np.append(np.ones(n - 1), 0.5)
12✔
255
        wts[n - 2 :: -2] = -1
12✔
256
        wts[0] = 0.5 * wts[0]
12✔
257
    return wts
12✔
258

259

260
def chebpts2(n):
12✔
261
    """Return n Chebyshev points of the second-kind"""
262
    if n == 1:
12✔
263
        pts = np.array([0.0])
12✔
264
    else:
265
        nn = np.arange(n)
12✔
266
        pts = np.cos(nn[::-1] * np.pi / (n - 1))
12✔
267
    return pts
12✔
268

269

270
def vals2coeffs2(vals):
12✔
271
    """Map function values at Chebyshev points of 2nd kind to
272
    first-kind Chebyshev polynomial coefficients"""
273
    n = vals.size
12✔
274
    if n <= 1:
12✔
275
        coeffs = vals
12✔
276
        return coeffs
12✔
277
    tmp = np.append(vals[::-1], vals[1:-1])
12✔
278
    if np.isreal(vals).all():
12✔
279
        coeffs = ifft(tmp)
12✔
280
        coeffs = np.real(coeffs)
12✔
281
    elif np.isreal(1j * vals).all():
12✔
282
        coeffs = ifft(np.imag(tmp))
×
283
        coeffs = 1j * np.real(coeffs)
×
284
    else:
285
        coeffs = ifft(tmp)
12✔
286
    coeffs = coeffs[:n]
12✔
287
    coeffs[1 : n - 1] = 2 * coeffs[1 : n - 1]
12✔
288
    return coeffs
12✔
289

290

291
def coeffs2vals2(coeffs):
12✔
292
    """Map first-kind Chebyshev polynomial coefficients to
293
    function values at Chebyshev points of 2nd kind"""
294
    n = coeffs.size
12✔
295
    if n <= 1:
12✔
296
        vals = coeffs
12✔
297
        return vals
12✔
298
    coeffs = coeffs.copy()
12✔
299
    coeffs[1 : n - 1] = 0.5 * coeffs[1 : n - 1]
12✔
300
    tmp = np.append(coeffs, coeffs[n - 2 : 0 : -1])
12✔
301
    if np.isreal(coeffs).all():
12✔
302
        vals = fft(tmp)
12✔
303
        vals = np.real(vals)
12✔
304
    elif np.isreal(1j * coeffs).all():
12✔
305
        vals = fft(np.imag(tmp))
×
306
        vals = 1j * np.real(vals)
×
307
    else:
308
        vals = fft(tmp)
12✔
309
    vals = vals[n - 1 :: -1]
12✔
310
    return vals
12✔
311

312

313
def newtonroots(fun, rts, tol=None, maxiter=None):
12✔
314
    """Rootfinding for a callable and differentiable fun, typically used to
315
    polish already computed roots."""
316
    tol = tol if tol is not None else 2 * prefs.eps
12✔
317
    maxiter = maxiter if maxiter is not None else prefs.maxiter
12✔
318
    if rts.size > 0:
12✔
319
        dfun = fun.diff()
12✔
320
        prv = np.inf * rts
12✔
321
        count = 0
12✔
322
        while (infnorm(rts - prv) > tol) & (count <= maxiter):
12✔
323
            count += 1
12✔
324
            prv = rts
12✔
325
            rts = rts - fun(rts) / dfun(rts)
12✔
326
    return rts
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc