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
« 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.
3This module provides the main interface for users to create Chebfun objects,
4which are the core data structure in ChebPy for representing functions.
5"""
7from collections.abc import Callable
8from typing import Any
10import numpy as np
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
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 )
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.
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.
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.
61 Returns:
62 Chebfun: A Chebfun object representing the function.
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.
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()
91 domain = domain if domain is not None else prefs.domain
93 # Callable fct in chebfun(lambda x: f(x), ... )
94 if callable(f):
95 return Chebfun.initfun(f, domain, n, sing=sing, params=params)
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)
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)
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
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)
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.
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.
156 Returns:
157 Chebfun: A scalar-valued Chebfun approximating the Floater-Hormann
158 rational interpolant through the equispaced samples.
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.
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)
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)
182def pwc(domain: list[float] | None = None, values: list[float] | None = None) -> "Chebfun":
183 """Initialize a piecewise-constant Chebfun.
185 Creates a piecewise-constant function represented as a Chebfun object.
186 The function takes constant values on each interval defined by the domain.
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].
193 Returns:
194 Chebfun: A piecewise-constant Chebfun object.
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)
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*.
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).
224 Args:
225 n: Number of Chebyshev points.
226 domain: Two-element list ``[a, b]`` specifying the interval.
227 Defaults to ``[-1, 1]``.
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.
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
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.
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*.
260 The API mirrors :func:`chebfun` exactly:
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
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.
274 Returns:
275 Chebfun: A Chebfun object whose pieces are backed by Trigtech.
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)