Coverage for src/chebpy/classicfun.py: 100%
163 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-22 21:50 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-22 21:50 +0000
1"""Implementation of the Classicfun class for functions on arbitrary intervals.
3This module provides the Classicfun class, which represents functions on arbitrary intervals
4by mapping them to a standard domain [-1, 1] and using a Onefun representation.
5"""
7from abc import ABC
8from typing import TYPE_CHECKING, Any, cast
10import matplotlib.pyplot as plt
11import numpy as np
13from .chebtech import Chebtech
14from .decorators import self_empty
15from .exceptions import IntervalMismatch, NotSubinterval
16from .fun import Fun
17from .plotting import plotfun
18from .settings import _preferences as prefs
19from .trigtech import Trigtech
20from .utilities import Interval, IntervalMap
22techdict = {
23 "Chebtech": Chebtech,
24 "Trigtech": Trigtech,
25}
28class Classicfun(Fun, ABC):
29 """Abstract base class for functions defined on arbitrary intervals using a mapped representation.
31 This class implements the Fun interface for functions defined on arbitrary intervals
32 by mapping them to a standard domain [-1, 1] and using a Onefun representation
33 (such as Chebtech) on that standard domain.
35 The Classicfun class serves as a base class for specific implementations like Bndfun.
36 It handles the mapping between the arbitrary interval and the standard domain,
37 delegating the actual function representation to the underlying Onefun object.
38 """
40 # ``_singularity_priority`` lets mixed-type binary operations dispatch
41 # to the more "singular" representation when two ``Classicfun``
42 # subclasses meet on the same interval. Higher wins. ``Bndfun`` and
43 # ``CompactFun`` use the default of ``0``; ``Singfun`` overrides to
44 # ``10`` so that ``Singfun + Bndfun`` yields a ``Singfun``.
45 _singularity_priority: int = 0
47 if TYPE_CHECKING:
48 # The algebra/utility methods below are attached to ``Classicfun`` at
49 # import time by the ``setattr`` blocks further down (they delegate to
50 # the underlying ``onefun``). They satisfy the abstract methods declared
51 # on :class:`Fun`; declaring them here lets static type checkers see the
52 # concrete implementations, so subclasses such as ``Bndfun`` are
53 # treated as instantiable and ``super().__op__()`` calls resolve safely.
54 def __add__(self, other: Any) -> Fun:
55 """Add another function or scalar (dynamically attached)."""
56 ...
58 def __sub__(self, other: Any) -> Fun:
59 """Subtract another function or scalar (dynamically attached)."""
60 ...
62 def __mul__(self, other: Any) -> Fun:
63 """Multiply by another function or scalar (dynamically attached)."""
64 ...
66 def __pow__(self, power: Any) -> Fun:
67 """Raise to a power (dynamically attached)."""
68 ...
70 def __radd__(self, other: Any) -> Fun:
71 """Right-hand addition (dynamically attached)."""
72 ...
74 def __rsub__(self, other: Any) -> Fun:
75 """Right-hand subtraction (dynamically attached)."""
76 ...
78 def __rmul__(self, other: Any) -> Fun:
79 """Right-hand multiplication (dynamically attached)."""
80 ...
82 def __neg__(self) -> Fun:
83 """Negate this function (dynamically attached)."""
84 ...
86 def __pos__(self) -> Fun:
87 """Return this function unchanged (dynamically attached)."""
88 ...
90 def copy(self) -> Fun:
91 """Return a deep copy (dynamically attached)."""
92 ...
94 def simplify(self) -> Fun:
95 """Return a simplified representation (dynamically attached)."""
96 ...
98 def values(self) -> np.ndarray:
99 """Return the function values at the representation points (dynamically attached)."""
100 ...
102 # --------------------------
103 # alternative constructors
104 # --------------------------
105 @classmethod
106 def initempty(cls) -> "Classicfun":
107 """Initialize an empty function.
109 This constructor creates an empty function representation, which is
110 useful as a placeholder or for special cases. The interval has no
111 relevance to the emptiness status of a Classicfun, so we arbitrarily
112 set it to be the default interval [-1, 1].
114 Returns:
115 Classicfun: A new empty instance.
116 """
117 interval = Interval()
118 onefun = techdict[prefs.tech].initempty(interval=interval)
119 return cls(onefun, interval)
121 @classmethod
122 def initconst(cls, c: Any, interval: Any) -> "Classicfun":
123 """Initialize a constant function.
125 This constructor creates a function that represents a constant value
126 on the specified interval.
128 Args:
129 c: The constant value.
130 interval: The interval on which to define the function.
132 Returns:
133 Classicfun: A new instance representing the constant function f(x) = c.
134 """
135 onefun = techdict[prefs.tech].initconst(c, interval=interval)
136 return cls(onefun, interval)
138 @classmethod
139 def initidentity(cls, interval: Any) -> "Classicfun":
140 """Initialize the identity function f(x) = x.
142 This constructor creates a function that represents f(x) = x
143 on the specified interval.
145 Args:
146 interval: The interval on which to define the identity function.
148 Returns:
149 Classicfun: A new instance representing the identity function.
150 """
151 onefun = techdict[prefs.tech].initvalues(np.asarray(interval), interval=interval)
152 return cls(onefun, interval)
154 @classmethod
155 def initfun_adaptive(cls, f: Any, interval: Any) -> "Classicfun":
156 """Initialize from a callable function using adaptive sampling.
158 This constructor determines the appropriate number of points needed to
159 represent the function to the specified tolerance using an adaptive algorithm.
161 Args:
162 f (callable): The function to be approximated.
163 interval: The interval on which to define the function.
165 Returns:
166 Classicfun: A new instance representing the function f.
167 """
168 onefun = techdict[prefs.tech].initfun(lambda y: f(interval(y)), interval=interval)
169 return cls(onefun, interval)
171 @classmethod
172 def initfun_fixedlen(cls, f: Any, interval: Any, n: int) -> "Classicfun":
173 """Initialize from a callable function using a fixed number of points.
175 This constructor uses a specified number of points to represent the function,
176 rather than determining the number adaptively.
178 Args:
179 f (callable): The function to be approximated.
180 interval: The interval on which to define the function.
181 n (int): The number of points to use.
183 Returns:
184 Classicfun: A new instance representing the function f.
185 """
186 onefun = techdict[prefs.tech].initfun(lambda y: f(interval(y)), n, interval=interval)
187 return cls(onefun, interval)
189 # -------------------
190 # 'private' methods
191 # -------------------
192 def __call__(self, x: Any, how: str = "clenshaw") -> Any:
193 """Evaluate the function at points x.
195 This method evaluates the function at the specified points by mapping them
196 to the standard domain [-1, 1] and evaluating the underlying onefun.
198 Args:
199 x (float or array-like): Points at which to evaluate the function.
200 how (str, optional): Method to use for evaluation. Defaults to "clenshaw".
202 Returns:
203 float or array-like: The value(s) of the function at the specified point(s).
204 Returns a scalar if x is a scalar, otherwise an array of the same size as x.
205 """
206 y = self.map.invmap(x)
207 return self.onefun(y, how)
209 def __init__(self, onefun: Any, interval: Any) -> None:
210 """Initialize a new Classicfun instance.
212 This method initializes a new function representation on the specified interval
213 using the provided onefun object for the standard domain representation.
215 Args:
216 onefun: The Onefun object representing the function on [-1, 1].
217 interval: The Interval object defining the domain of the function.
218 """
219 self.onefun = onefun
220 self._interval = interval
222 def _rebuild(self, onefun: Any) -> "Classicfun":
223 """Construct a new instance of this class with a replacement ``onefun``.
225 Subclasses that carry additional metadata beyond ``onefun`` and
226 ``interval`` (e.g. :class:`CompactFun`'s logical interval) should
227 override this method so that operations defined on the parent class
228 preserve that metadata.
230 Args:
231 onefun: The replacement Onefun object.
233 Returns:
234 Classicfun: A new instance of ``type(self)``.
235 """
236 return self.__class__(onefun, self._interval)
238 def _can_share_onefun_with(self, other: "Classicfun") -> bool:
239 """Return True if ``self`` and ``other`` represent functions on the same t-grid.
241 Two ``Classicfun`` instances can share onefun-level arithmetic when
242 they have the same concrete subclass, the same logical interval, and
243 the same map (so the underlying ``Onefun`` coefficients refer to the
244 same Chebyshev nodes in ``t``-space). The default implementation
245 compares only the type and the interval, which is correct for the
246 affine-mapped subclasses (:class:`Bndfun`, :class:`CompactFun`).
247 :class:`~chebpy.singfun.Singfun` overrides this to additionally
248 compare maps.
249 """
250 return type(self) is type(other) and self._interval == other._interval
252 def _rebuild_from_callable(self, f: Any) -> "Classicfun":
253 """Adaptively rebuild a fun of this type evaluating callable ``f``.
255 Used by mixed-type binary operations to reconstruct the result on the
256 dominant operand's representation. Subclasses with extra metadata
257 (e.g. :class:`~chebpy.singfun.Singfun`'s map) override this.
258 """
259 return type(self).initfun_adaptive(f, self._interval)
261 def __repr__(self) -> str: # pragma: no cover
262 """Return a string representation of the function.
264 This method returns a string representation of the function that includes
265 the class name, support interval, and size.
267 Returns:
268 str: A string representation of the function.
269 """
270 out = "{0}([{2}, {3}], {1})".format(self.__class__.__name__, self.size, *self.support)
271 return out
273 # ------------
274 # properties
275 # ------------
276 @property
277 def coeffs(self) -> Any:
278 """Get the coefficients of the function representation.
280 This property returns the coefficients used in the function representation,
281 delegating to the underlying onefun object.
283 Returns:
284 array-like: The coefficients of the function representation.
285 """
286 return self.onefun.coeffs
288 @property
289 def endvalues(self) -> Any:
290 """Get the values of the function at the endpoints of its interval.
292 This property evaluates the function at the endpoints of its interval
293 of definition.
295 Returns:
296 numpy.ndarray: Array containing the function values at the endpoints
297 of the interval [a, b].
298 """
299 return self.__call__(self.support)
301 @property
302 def interval(self) -> Any:
303 """Get the interval on which this function is defined.
305 This property returns the interval object representing the domain
306 of definition for this function.
308 Returns:
309 Interval: The interval on which this function is defined.
310 """
311 return self._interval
313 @property
314 def map(self) -> IntervalMap:
315 """Return the bijective map between [-1, 1] and the function's interval.
317 Subclasses backed by a non-affine map (e.g. endpoint-clustering
318 transforms for endpoint singularities) override this to return a
319 different :class:`~chebpy.utilities.IntervalMap` implementer while
320 keeping ``self._interval`` as the logical support endpoints.
322 Returns:
323 IntervalMap: The map used to relate reference points ``y ∈ [-1, 1]``
324 to logical points ``x ∈ [a, b]``. Defaults to ``self._interval``,
325 which is the affine :class:`~chebpy.utilities.Interval` map.
326 """
327 return cast(IntervalMap, self._interval)
329 @property
330 def isconst(self) -> Any:
331 """Check if this function represents a constant.
333 This property determines whether the function is constant (i.e., f(x) = c
334 for some constant c) over its interval of definition, delegating to the
335 underlying onefun object.
337 Returns:
338 bool: True if the function is constant, False otherwise.
339 """
340 return self.onefun.isconst
342 @property
343 def iscomplex(self) -> Any:
344 """Check if this function has complex values.
346 This property determines whether the function has complex values or is
347 purely real-valued, delegating to the underlying onefun object.
349 Returns:
350 bool: True if the function has complex values, False otherwise.
351 """
352 return self.onefun.iscomplex
354 @property
355 def isempty(self) -> Any:
356 """Check if this function is empty.
358 This property determines whether the function is empty, which is a special
359 state used as a placeholder or for special cases, delegating to the
360 underlying onefun object.
362 Returns:
363 bool: True if the function is empty, False otherwise.
364 """
365 return self.onefun.isempty
367 @property
368 def size(self) -> Any:
369 """Get the size of the function representation.
371 This property returns the number of coefficients or other measure of the
372 complexity of the function representation, delegating to the underlying
373 onefun object.
375 Returns:
376 int: The size of the function representation.
377 """
378 return self.onefun.size
380 @property
381 def support(self) -> Any:
382 """Get the support interval of this function.
384 This property returns the interval on which this function is defined,
385 represented as a numpy array with two elements [a, b].
387 Returns:
388 numpy.ndarray: Array containing the endpoints of the interval.
389 """
390 return np.asarray(self.interval)
392 @property
393 def vscale(self) -> Any:
394 """Get the vertical scale of the function.
396 This property returns a measure of the range of function values, typically
397 the maximum absolute value of the function on its interval of definition,
398 delegating to the underlying onefun object.
400 Returns:
401 float: The vertical scale of the function.
402 """
403 return self.onefun.vscale
405 # -----------
406 # utilities
407 # -----------
409 def imag(self) -> "Classicfun":
410 """Get the imaginary part of this function.
412 This method returns a new function representing the imaginary part of this function.
413 If this function is real-valued, returns a zero function.
415 Returns:
416 Classicfun: A new function representing the imaginary part of this function.
417 """
418 if self.iscomplex:
419 return self._rebuild(self.onefun.imag())
420 else:
421 return self.initconst(0, interval=self.interval)
423 def real(self) -> "Classicfun":
424 """Get the real part of this function.
426 This method returns a new function representing the real part of this function.
427 If this function is already real-valued, returns this function.
429 Returns:
430 Classicfun: A new function representing the real part of this function.
431 """
432 if self.iscomplex:
433 return self._rebuild(self.onefun.real())
434 else:
435 return self
437 def restrict(self, subinterval: Any) -> "Classicfun":
438 """Restrict this function to a subinterval.
440 This method creates a new function that is the restriction of this function
441 to the specified subinterval. The output is formed using a fixed length
442 construction with the same number of degrees of freedom as the original function.
444 Args:
445 subinterval (array-like): The subinterval to which this function should be restricted.
446 Must be contained within the original interval of definition.
448 Returns:
449 Classicfun: A new function representing the restriction of this function to the subinterval.
451 Raises:
452 NotSubinterval: If the subinterval is not contained within the original interval.
453 """
454 if subinterval not in self.interval: # pragma: no cover
455 raise NotSubinterval(self.interval, subinterval)
456 if self.interval == subinterval:
457 return self
458 else:
459 return self.__class__.initfun_fixedlen(self, subinterval, self.size)
461 def translate(self, c: float) -> "Classicfun":
462 """Translate this function by a constant c.
464 This method creates a new function g(x) = f(x-c), which is the original
465 function translated horizontally by c.
467 Args:
468 c (float): The amount by which to translate the function.
470 Returns:
471 Classicfun: A new function representing g(x) = f(x-c).
472 """
473 return self.__class__(self.onefun, self.interval + c)
475 # -------------
476 # rootfinding
477 # -------------
478 def roots(self) -> Any:
479 """Find the roots (zeros) of the function on its interval of definition.
481 This method computes the points where the function equals zero
482 within its interval of definition by finding the roots of the
483 underlying onefun and mapping them to the function's interval.
485 Returns:
486 numpy.ndarray: An array of the roots of the function in its interval of definition,
487 sorted in ascending order.
488 """
489 uroots = self.onefun.roots()
490 return self.map.formap(uroots)
492 # ----------
493 # calculus
494 # ----------
495 def cumsum(self) -> "Classicfun":
496 """Compute the indefinite integral of the function.
498 This method calculates the indefinite integral (antiderivative) of the function,
499 with the constant of integration chosen so that the indefinite integral
500 evaluates to 0 at the left endpoint of the interval.
502 Returns:
503 Classicfun: A new function representing the indefinite integral of this function.
504 """
505 a, b = self.interval
506 onefun = 0.5 * (b - a) * self.onefun.cumsum()
507 return self._rebuild(onefun)
509 def diff(self) -> "Classicfun":
510 """Compute the derivative of the function.
512 This method calculates the derivative of the function with respect to x,
513 applying the chain rule to account for the mapping between the standard
514 domain [-1, 1] and the function's interval.
516 Returns:
517 Classicfun: A new function representing the derivative of this function.
518 """
519 a, b = self.interval
520 onefun = 2.0 / (b - a) * self.onefun.diff()
521 return self._rebuild(onefun)
523 def sum(self) -> Any:
524 """Compute the definite integral of the function over its interval of definition.
526 This method calculates the definite integral of the function
527 over its interval of definition [a, b], applying the appropriate
528 scaling factor to account for the mapping from [-1, 1].
530 Returns:
531 float or complex: The definite integral of the function over its interval of definition.
532 """
533 a, b = self.interval
534 return 0.5 * (b - a) * self.onefun.sum()
536 # ----------
537 # plotting
538 # ----------
539 def plot(self, ax: Any = None, **kwds: Any) -> Any:
540 """Plot the function over its interval of definition.
542 This method plots the function over its interval of definition using matplotlib.
543 For complex-valued functions, it plots the real part against the imaginary part.
545 Args:
546 ax (matplotlib.axes.Axes, optional): The axes on which to plot. If None,
547 a new axes will be created. Defaults to None.
548 **kwds: Additional keyword arguments to pass to matplotlib's plot function.
550 Returns:
551 matplotlib.axes.Axes: The axes on which the plot was created.
552 """
553 return plotfun(self, self.support, ax=ax, **kwds)
556# ----------------------------------------------------------------
557# methods that execute the corresponding onefun method as is
558# ----------------------------------------------------------------
560methods_onefun_other = ("values", "plotcoeffs")
563def add_utility(methodname: str) -> None:
564 """Add a utility method to the Classicfun class.
566 This function creates a method that delegates to the corresponding method
567 of the underlying onefun object and adds it to the Classicfun class.
569 Args:
570 methodname (str): The name of the method to add.
572 Note:
573 The created method will have the same name and signature as the
574 corresponding method in the onefun object.
575 """
577 def method(self: Any, *args: Any, **kwds: Any) -> Any:
578 """Delegate to the corresponding method of the underlying onefun object.
580 This method calls the same-named method on the underlying onefun object
581 and returns its result.
583 Args:
584 self (Classicfun): The Classicfun object.
585 *args: Variable length argument list to pass to the onefun method.
586 **kwds: Arbitrary keyword arguments to pass to the onefun method.
588 Returns:
589 The return value from the corresponding onefun method.
590 """
591 return getattr(self.onefun, methodname)(*args, **kwds)
593 method.__name__ = methodname
594 method.__doc__ = method.__doc__
595 setattr(Classicfun, methodname, method)
598for methodname in methods_onefun_other:
599 if methodname[:4] == "plot" and plt is None: # pragma: no cover
600 continue
601 add_utility(methodname)
604# -----------------------------------------------------------------------
605# unary operators and zero-argument utlity methods returning a onefun
606# -----------------------------------------------------------------------
608methods_onefun_zeroargs = ("__pos__", "__neg__", "copy", "simplify")
611def add_zero_arg_op(methodname: str) -> None:
612 """Add a zero-argument operation method to the Classicfun class.
614 This function creates a method that delegates to the corresponding method
615 of the underlying onefun object and wraps the result in a new Classicfun
616 instance with the same interval.
618 Args:
619 methodname (str): The name of the method to add.
621 Note:
622 The created method will have the same name and signature as the
623 corresponding method in the onefun object, but will return a Classicfun
624 instance instead of an onefun instance.
625 """
627 def method(self: Any, *args: Any, **kwds: Any) -> Any:
628 """Apply a zero-argument operation and return a new Classicfun.
630 This method calls the same-named method on the underlying onefun object
631 and wraps the result in a new Classicfun instance with the same interval.
633 Args:
634 self (Classicfun): The Classicfun object.
635 *args: Variable length argument list to pass to the onefun method.
636 **kwds: Arbitrary keyword arguments to pass to the onefun method.
638 Returns:
639 Classicfun: A new Classicfun instance with the result of the operation.
640 """
641 onefun = getattr(self.onefun, methodname)(*args, **kwds)
642 return self._rebuild(onefun)
644 method.__name__ = methodname
645 method.__doc__ = method.__doc__
646 setattr(Classicfun, methodname, method)
649for methodname in methods_onefun_zeroargs:
650 add_zero_arg_op(methodname)
652# -----------------------------------------
653# binary operators returning a onefun
654# -----------------------------------------
656# Map from dunder method name to the corresponding callable acting on raw
657# values. Used by the mixed-subclass binary-op fallback to reconstruct the
658# result adaptively on the dominant operand's representation.
659_BINOP_OPERATORS: dict[str, Any] = {
660 "__add__": lambda a, b: a + b,
661 "__sub__": lambda a, b: a - b,
662 "__mul__": lambda a, b: a * b,
663 "__truediv__": lambda a, b: a / b,
664 "__div__": lambda a, b: a / b,
665 "__pow__": lambda a, b: a**b,
666 "__radd__": lambda a, b: b + a,
667 "__rsub__": lambda a, b: b - a,
668 "__rmul__": lambda a, b: b * a,
669 "__rtruediv__": lambda a, b: b / a,
670 "__rdiv__": lambda a, b: b / a,
671 "__rpow__": lambda a, b: b**a,
672}
675def _classicfun_mixed_binop(self: "Classicfun", other: "Classicfun", methodname: str) -> "Classicfun":
676 """Reconstruct a same-interval, mixed-subclass binary op on the dominant operand.
678 When two :class:`Classicfun` instances of different subclasses (or
679 same subclass but with maps that disagree) meet on the same logical
680 interval, neither's onefun-level arithmetic is correct. Pick the
681 operand with higher ``_singularity_priority`` and rebuild the
682 composition adaptively in its representation. Ties go to ``self``.
683 """
684 op_fn = _BINOP_OPERATORS[methodname]
685 owner = self if self._singularity_priority >= other._singularity_priority else other
687 def combined(x: Any) -> Any:
688 """Evaluate the binary op pointwise on both operands at *x*."""
689 return op_fn(self(x), other(x))
691 return owner._rebuild_from_callable(combined)
694# ToDo: change these to operator module methods
695methods_onefun_binary = (
696 "__add__",
697 "__div__",
698 "__mul__",
699 "__pow__",
700 "__radd__",
701 "__rdiv__",
702 "__rmul__",
703 "__rpow__",
704 "__rsub__",
705 "__rtruediv__",
706 "__sub__",
707 "__truediv__",
708)
711def add_binary_op(methodname: str) -> None:
712 """Add a binary operation method to the Classicfun class.
714 This function creates a method that implements a binary operation between
715 two Classicfun objects or between a Classicfun and a scalar. It delegates
716 to the corresponding method of the underlying onefun object and wraps the
717 result in a new Classicfun instance with the same interval.
719 Args:
720 methodname (str): The name of the binary operation method to add.
722 Note:
723 The created method will check that both Classicfun objects have the
724 same interval before performing the operation. If one operand is not
725 a Classicfun, it will be passed directly to the onefun method.
726 """
728 @self_empty()
729 def method(self: Any, f: Any, *args: Any, **kwds: Any) -> Any:
730 """Apply a binary operation and return a new Classicfun.
732 This method implements a binary operation between this Classicfun and
733 another object (either another Classicfun or a scalar). It delegates
734 to the corresponding method of the underlying onefun object and wraps
735 the result in a new Classicfun instance with the same interval.
737 Args:
738 self (Classicfun): The Classicfun object.
739 f (Classicfun or scalar): The second operand of the binary operation.
740 *args: Variable length argument list to pass to the onefun method.
741 **kwds: Arbitrary keyword arguments to pass to the onefun method.
743 Returns:
744 Classicfun: A new Classicfun instance with the result of the operation.
746 Raises:
747 IntervalMismatch: If f is a Classicfun with a different interval.
748 """
749 if isinstance(f, Classicfun):
750 if f.isempty:
751 return f.copy()
752 if self.interval != f.interval: # pragma: no cover
753 raise IntervalMismatch(self.interval, f.interval)
754 if not self._can_share_onefun_with(f):
755 # Mixed subclasses (or same subclass with disagreeing maps):
756 # rebuild adaptively on the dominant operand's representation.
757 return _classicfun_mixed_binop(self, f, methodname)
758 g = f.onefun
759 else:
760 # let the lower level classes raise any other exceptions
761 g = f
762 onefun = getattr(self.onefun, methodname)(g, *args, **kwds)
763 return self._rebuild(onefun)
765 method.__name__ = methodname
766 method.__doc__ = method.__doc__
767 setattr(Classicfun, methodname, method)
770for methodname in methods_onefun_binary:
771 add_binary_op(methodname)
773# ---------------------------
774# numpy universal functions
775# ---------------------------
778def add_ufunc(op: Any) -> None:
779 """Add a NumPy universal function method to the Classicfun class.
781 This function creates a method that applies a NumPy universal function (ufunc)
782 to the values of a Classicfun and returns a new Classicfun representing the result.
784 Args:
785 op (callable): The NumPy universal function to apply.
787 Note:
788 The created method will have the same name as the NumPy function
789 and will take no arguments other than self.
790 """
792 @self_empty()
793 def method(self: Any) -> Any:
794 """Apply a NumPy universal function to this function.
796 This method applies a NumPy universal function (ufunc) to the values
797 of this function and returns a new function representing the result.
799 Returns:
800 Classicfun: A new function representing op(f(x)).
801 """
802 return self.__class__.initfun_adaptive(lambda x: op(self(x)), self.interval)
804 name = op.__name__
805 method.__name__ = name
806 method.__doc__ = method.__doc__
807 setattr(Classicfun, name, method)
810ufuncs = (
811 np.absolute,
812 np.arccos,
813 np.arccosh,
814 np.arcsin,
815 np.arcsinh,
816 np.arctan,
817 np.arctanh,
818 np.ceil,
819 np.cos,
820 np.cosh,
821 np.exp,
822 np.exp2,
823 np.expm1,
824 np.floor,
825 np.log,
826 np.log2,
827 np.log10,
828 np.log1p,
829 np.sign,
830 np.sinh,
831 np.sin,
832 np.tan,
833 np.tanh,
834 np.sqrt,
835)
837for op in ufuncs:
838 add_ufunc(op)