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

35 statements  

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

1"""Construction of piecewise Chebfuns with endpoint singularities. 

2 

3Kept separate from :mod:`chebpy.chebfun` so the singularity-piece plumbing 

4does not bloat the main class; it is used by the ``sing``-aware Chebfun 

5constructors via :func:`generate_singular_funs`. 

6""" 

7 

8from __future__ import annotations 

9 

10from collections.abc import Callable 

11from typing import Any 

12 

13from .bndfun import Bndfun 

14from .exceptions import InvalidSingularitySide 

15from .maps import MapParams 

16from .settings import _preferences as prefs 

17from .singfun import Singfun 

18from .utilities import Domain 

19 

20 

21def _piece_singularity(sing: str, is_first: bool, is_last: bool) -> str | None: 

22 """Return the singularity side for one piece, or ``None`` for a smooth piece. 

23 

24 Maps the whole-domain ``sing`` request onto the side (if any) that a 

25 single piece carries, given whether it is the first and/or last piece. 

26 

27 Args: 

28 sing: One of ``"left"``, ``"right"``, ``"both"``. 

29 is_first: Whether the piece is the leftmost in the domain. 

30 is_last: Whether the piece is the rightmost in the domain. 

31 

32 Returns: 

33 ``"left"``, ``"right"``, ``"both"``, or ``None`` (a smooth Bndfun piece). 

34 """ 

35 left = is_first and sing in ("left", "both") 

36 right = is_last and sing in ("right", "both") 

37 if left and right: 

38 return "both" 

39 if left: 

40 return "left" 

41 if right: 

42 return "right" 

43 return None 

44 

45 

46def generate_singular_funs( 

47 f: Callable[..., Any], 

48 domain: Any, 

49 *, 

50 sing: str, 

51 params: Any, 

52) -> list[Any]: 

53 """Build per-piece funs for a Chebfun with endpoint singularities. 

54 

55 The leftmost / rightmost pieces (depending on ``sing``) are built as 

56 :class:`~chebpy.singfun.Singfun` instances using the Adcock-Richardson 

57 clustering map; all interior pieces are ordinary :class:`Bndfun`. 

58 

59 Args: 

60 f: Callable evaluating the function in logical coordinates. 

61 domain: Breakpoint sequence; outermost endpoints must be finite. 

62 sing: One of ``"left"``, ``"right"``, ``"both"``. 

63 params: A :class:`~chebpy.maps.MapParams` instance carrying 

64 ``(L, alpha)`` for the slit-strip clustering map. ``None`` means 

65 use :class:`~chebpy.maps.MapParams` defaults. 

66 

67 Returns: 

68 list: Per-piece funs ready to feed to :class:`Chebfun`. 

69 

70 Raises: 

71 InvalidSingularitySide: If ``sing`` is not one of the recognised values. 

72 """ 

73 if sing not in ("left", "right", "both"): 

74 msg = f"sing must be 'left', 'right', or 'both'; got {sing!r}" 

75 raise InvalidSingularitySide(msg) 

76 if params is None: 

77 params = MapParams() 

78 dom = Domain(domain if domain is not None else prefs.domain) 

79 intervals = list(dom.intervals) 

80 n_pieces = len(intervals) 

81 funs: list[Any] = [] 

82 for i, interval in enumerate(intervals): 

83 piece_sing = _piece_singularity(sing, i == 0, i == n_pieces - 1) 

84 if piece_sing is None: 

85 funs.append(Bndfun.initfun_adaptive(f, interval)) 

86 else: 

87 funs.append(Singfun.initfun_adaptive(f, interval, sing=piece_sing, params=params)) 

88 return funs