Coverage for src/chebpy/api.py: 100%

81 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-01 13:43 +0000

1"""User-facing functions for creating and manipulating Chebfun objects. 

2 

3This module provides the main interface for users to create Chebfun objects, 

4which are the core data structure in ChebPy for representing functions. 

5""" 

6 

7from collections.abc import Callable 

8from typing import Any 

9 

10import numpy as np 

11 

12from .algorithms import barywts2, chebpts2, funqui 

13from .bndfun import Bndfun 

14from .chebfun import Chebfun 

15from .exceptions import InvalidDomain 

16from .settings import _preferences as prefs 

17from .utilities import Domain, Interval 

18 

19 

20def _cannot_construct(f: object) -> str: 

21 """Return the error message for an input ``chebfun`` cannot turn into a constant.""" 

22 return ( 

23 f"cannot construct a Chebfun from {f!r}: expected None, a callable, " 

24 "a single alphabetic character such as 'x', or a number" 

25 ) 

26 

27 

28def chebfun( 

29 f: Callable[..., Any] | str | float | None = None, 

30 domain: np.ndarray | list[float] | None = None, 

31 n: int | None = None, 

32 *, 

33 sing: str | None = None, 

34 params: Any = None, 

35) -> "Chebfun": 

36 """Create a Chebfun object representing a function. 

37 

38 A Chebfun object represents a function using Chebyshev polynomials. This constructor 

39 can create Chebfun objects from various inputs including callable functions, 

40 constants, and special strings. 

41 

42 Args: 

43 f: The function to represent. Can be: 

44 - None: Creates an empty Chebfun 

45 - callable: A function handle like lambda x: x**2 

46 - str: A single alphabetic character (e.g., 'x') for the identity function 

47 - numeric: A constant value 

48 domain: The domain on which to define the function. Defaults to the domain 

49 specified in preferences. 

50 n: Optional number of points to use in the discretization. If None, adaptive 

51 construction is used. 

52 sing: Optional endpoint-singularity hint, one of ``"left"``, ``"right"``, 

53 or ``"both"``. When set, the appropriate boundary pieces are built 

54 as :class:`~chebpy.singfun.Singfun` instances using the 

55 Adcock-Richardson exponential clustering map; interior pieces remain 

56 :class:`~chebpy.bndfun.Bndfun`. Only supported with ``n=None``. 

57 params: Slit-strip map parameters (a :class:`~chebpy.maps.MapParams` 

58 carrying ``L`` and ``alpha``). Default ``None`` uses 

59 :class:`~chebpy.maps.MapParams` defaults. 

60 

61 Returns: 

62 Chebfun: A Chebfun object representing the function. 

63 

64 Raises: 

65 TypeError: If ``f`` is of a type that cannot be converted to a float at 

66 all, such as a list or a dict. 

67 ValueError: If ``f`` is of a convertible type but holds no usable value, 

68 such as a non-numeric string or an out-of-range integer. 

69 

70 Examples: 

71 >>> # Empty Chebfun 

72 >>> f = chebfun() 

73 >>> 

74 >>> # Function from a lambda 

75 >>> import numpy as np 

76 >>> f = chebfun(lambda x: np.sin(x), domain=[-np.pi, np.pi]) 

77 >>> 

78 >>> # Identity function 

79 >>> x = chebfun('x') 

80 >>> 

81 >>> # Constant function 

82 >>> c = chebfun(3.14) 

83 >>> 

84 >>> # Function with an endpoint singularity 

85 >>> g = chebfun(np.sqrt, domain=[0.0, 1.0], sing="left") 

86 """ 

87 # Empty via chebfun() 

88 if f is None: 

89 return Chebfun.initempty() 

90 

91 domain = domain if domain is not None else prefs.domain 

92 

93 # Callable fct in chebfun(lambda x: f(x), ... ) 

94 if callable(f): 

95 return Chebfun.initfun(f, domain, n, sing=sing, params=params) 

96 

97 # Identity via chebfun('x', ... ) 

98 if isinstance(f, str) and len(f) == 1 and f.isalpha(): 

99 if n: 

100 return Chebfun.initfun(lambda x: x, domain, n) 

101 else: 

102 return Chebfun.initidentity(domain) 

103 

104 # Constant fct via chebfun(3.14, ... ), chebfun('3.14', ... ). 

105 # Only the conversion is guarded: a failure inside initconst is about the domain, 

106 # not about f, and must not be relabelled as if f were at fault. 

107 try: 

108 value = float(f) 

109 except TypeError as err: 

110 # A wrong *type* of argument (list, dict, ...) stays a TypeError, so callers 

111 # already catching it keep working; only the message improves. 

112 raise TypeError(_cannot_construct(f)) from err 

113 except (OverflowError, ValueError) as err: 

114 raise ValueError(_cannot_construct(f)) from err 

115 return Chebfun.initconst(value, domain) 

116 

117 

118def _validated_samples(values: np.ndarray | list[float | complex]) -> np.ndarray: 

119 """Return *values* as a one-dimensional numeric array, or raise ValueError.""" 

120 vals = np.asarray(values) 

121 if vals.size == 0: 

122 msg = "values must be non-empty" 

123 raise ValueError(msg) 

124 if vals.ndim != 1: 

125 msg = "values must be one-dimensional" 

126 raise ValueError(msg) 

127 if not np.issubdtype(vals.dtype, np.number): 

128 msg = "values must be numeric" 

129 raise ValueError(msg) 

130 return vals 

131 

132 

133def _validated_interval(domain: np.ndarray | list[float] | None) -> Domain: 

134 """Return *domain* as a Domain of two finite increasing endpoints, or raise InvalidDomain.""" 

135 dom = np.asarray(prefs.domain if domain is None else domain, dtype=float) 

136 if dom.ndim != 1 or dom.size != 2: 

137 msg = "domain must contain exactly two endpoints" 

138 raise InvalidDomain(msg) 

139 if not np.all(np.isfinite(dom)): 

140 msg = "domain endpoints must be finite" 

141 raise InvalidDomain(msg) 

142 if dom[0] >= dom[1]: 

143 msg = "domain endpoints must be strictly increasing" 

144 raise InvalidDomain(msg) 

145 return Domain(dom) 

146 

147 

148def equifun(values: np.ndarray | list[float | complex], domain: np.ndarray | list[float] | None = None) -> "Chebfun": 

149 """Create a Chebfun from equispaced samples including both endpoints. 

150 

151 Args: 

152 values: Non-empty one-dimensional sample values. 

153 domain: Two finite endpoints for the sample interval. Defaults to 

154 the configured preference domain. 

155 

156 Returns: 

157 Chebfun: A scalar-valued Chebfun approximating the Floater-Hormann 

158 rational interpolant through the equispaced samples. 

159 

160 Raises: 

161 ValueError: If values are empty, non-numeric, or not one-dimensional. 

162 InvalidDomain: If domain is not exactly two finite, strictly 

163 increasing endpoints. 

164 

165 Examples: 

166 >>> import numpy as np 

167 >>> from chebpy import equifun 

168 >>> x = np.linspace(-1, 1, 25) 

169 >>> f = equifun(np.sin(x)) 

170 >>> bool(abs(f(0.0)) < 1e-12) 

171 True 

172 """ 

173 vals = _validated_samples(values) 

174 dom = _validated_interval(domain) 

175 

176 if vals.size == 1: 

177 value = complex(vals[0]) if np.iscomplexobj(vals) else vals[0] 

178 return Chebfun.initconst(value, dom) 

179 return Chebfun.initfun(funqui(vals, dom), dom) 

180 

181 

182def pwc(domain: list[float] | None = None, values: list[float] | None = None) -> "Chebfun": 

183 """Initialize a piecewise-constant Chebfun. 

184 

185 Creates a piecewise-constant function represented as a Chebfun object. 

186 The function takes constant values on each interval defined by the domain. 

187 

188 Args: 

189 domain (list): A list of breakpoints defining the intervals. Must have 

190 length equal to len(values) + 1. Default is [-1, 0, 1]. 

191 values (list): A list of constant values for each interval. Default is [0, 1]. 

192 

193 Returns: 

194 Chebfun: A piecewise-constant Chebfun object. 

195 

196 Examples: 

197 >>> # Create a step function with value 0 on [-1,0] and 1 on [0,1] 

198 >>> f = pwc() 

199 >>> 

200 >>> # Create a custom piecewise-constant function 

201 >>> f = pwc(domain=[-2, -1, 0, 1, 2], values=[-1, 0, 1, 2]) 

202 """ 

203 if values is None: 

204 values = [0, 1] 

205 if domain is None: 

206 domain = [-1, 0, 1] 

207 funs: list[Any] = [] 

208 intervals = list(Domain(domain).intervals) 

209 for interval, value in zip(intervals, values, strict=False): 

210 funs.append(Bndfun.initconst(value, interval)) 

211 return Chebfun(funs) 

212 

213 

214def chebpts( 

215 n: int, 

216 domain: list[float] | None = None, 

217) -> tuple[np.ndarray, np.ndarray]: 

218 """Return *n* Chebyshev points and barycentric weights on *domain*. 

219 

220 This provides the same functionality as MATLAB's ``chebpts(n, [a, b])``. 

221 The points are Chebyshev points of the second kind (i.e. the extrema of 

222 the Chebyshev polynomial T_{n-1} plus the endpoints). 

223 

224 Args: 

225 n: Number of Chebyshev points. 

226 domain: Two-element list ``[a, b]`` specifying the interval. 

227 Defaults to ``[-1, 1]``. 

228 

229 Returns: 

230 A ``(points, weights)`` tuple where *points* is an array of *n* 

231 Chebyshev points on the given domain and *weights* is the 

232 corresponding array of barycentric interpolation weights. 

233 

234 Examples: 

235 >>> pts, wts = chebpts(4) 

236 >>> pts, wts = chebpts(4, [0, 3]) 

237 """ 

238 if domain is None: 

239 domain = [-1, 1] 

240 pts = chebpts2(n) 

241 wts = barywts2(n) 

242 interval = Interval(*domain) 

243 pts = interval(pts) 

244 return pts, wts 

245 

246 

247def trigfun( 

248 f: Callable[..., Any] | str | float | None = None, 

249 domain: np.ndarray | list[float] | None = None, 

250 n: int | None = None, 

251) -> "Chebfun": 

252 """Create a Chebfun backed by Fourier (trigonometric) technology. 

253 

254 This is the explicit entry point for constructing periodic functions. 

255 Unlike ``chebfun``, which always uses Chebyshev polynomial technology, 

256 ``trigfun`` always uses :class:`~chebpy.trigtech.Trigtech` as the 

257 underlying approximation technology. The user is responsible for 

258 ensuring that *f* is smooth and periodic on *domain*. 

259 

260 The API mirrors :func:`chebfun` exactly: 

261 

262 * ``trigfun()`` → empty Chebfun 

263 * ``trigfun(lambda x: np.sin(np.pi*x), [-1, 1])`` → from callable 

264 * ``trigfun('x')`` → identity (not truly periodic; provided for 

265 interface compatibility) 

266 * ``trigfun(3.14)`` → constant function 

267 

268 Args: 

269 f: The function to represent. Same semantics as :func:`chebfun`. 

270 domain: Domain ``[a, b]``. Defaults to ``prefs.domain``. 

271 n: Fixed number of Fourier modes. If None, adaptive construction 

272 is used. 

273 

274 Returns: 

275 Chebfun: A Chebfun object whose pieces are backed by Trigtech. 

276 

277 Examples: 

278 >>> import numpy as np 

279 >>> from chebpy import trigfun 

280 >>> f = trigfun(lambda x: np.cos(np.pi * x), [-1, 1]) 

281 >>> float(f(0.0)) 

282 1.0 

283 >>> g = trigfun(lambda x: np.sin(2 * np.pi * x)) 

284 >>> bool(abs(g.sum()) < 1e-12) 

285 True 

286 """ 

287 with prefs: 

288 prefs.tech = "Trigtech" 

289 return chebfun(f, domain, n)