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

34 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-22 21:50 +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 .settings import _preferences as prefs 

15from .utilities import Domain 

16 

17 

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

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

20 

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

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

23 

24 Args: 

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

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

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

28 

29 Returns: 

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

31 """ 

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

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

34 if left and right: 

35 return "both" 

36 if left: 

37 return "left" 

38 if right: 

39 return "right" 

40 return None 

41 

42 

43def generate_singular_funs( 

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

45 domain: Any, 

46 *, 

47 sing: str, 

48 params: Any, 

49) -> list[Any]: 

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

51 

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

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

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

55 

56 Args: 

57 f: Callable evaluating the function in logical coordinates. 

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

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

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

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

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

63 

64 Returns: 

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

66 

67 Raises: 

68 ValueError: If ``sing`` is not one of the recognised values. 

69 """ 

70 # Local import: chebfun and singfun are siblings under classicfun and 

71 # would otherwise risk a cyclic top-level import. 

72 from .maps import MapParams 

73 from .singfun import Singfun 

74 

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

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

77 raise ValueError(msg) 

78 if params is None: 

79 params = MapParams() 

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

81 intervals = list(dom.intervals) 

82 n_pieces = len(intervals) 

83 funs: list[Any] = [] 

84 for i, interval in enumerate(intervals): 

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

86 if piece_sing is None: 

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

88 else: 

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

90 return funs