Coverage for src/lsqfitgp/_Deriv.py: 97%

64 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-15 19:54 +0000

1# lsqfitgp/_Deriv.py 

2# 

3# Copyright (c) 2020, 2022, 2024, Giacomo Petrillo 

4# 

5# This file is part of lsqfitgp. 

6# 

7# lsqfitgp is free software: you can redistribute it and/or modify 

8# it under the terms of the GNU General Public License as published by 

9# the Free Software Foundation, either version 3 of the License, or 

10# (at your option) any later version. 

11# 

12# lsqfitgp is distributed in the hope that it will be useful, 

13# but WITHOUT ANY WARRANTY; without even the implied warranty of 

14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

15# GNU General Public License for more details. 

16# 

17# You should have received a copy of the GNU General Public License 

18# along with lsqfitgp. If not, see <http://www.gnu.org/licenses/>. 

19 

20import collections 1efabcd

21 

22import numpy as np 1efabcd

23 

24class Deriv: 1efabcd

25 """ 

26 Class for specifying derivatives. Behaves like a dictionary str -> int, 

27 where the keys represent variables and values the derivation order. An 

28 empty Deriv means no derivatives. A Deriv with one single key None means 

29 that the variable is implicit. 

30 

31 Deriv(int) -> specified order derivative 

32  

33 Deriv(str) -> first derivative w.r.t. specified variable 

34  

35 Deriv(iter of str) -> derivative w.r.t. specified variables 

36  

37 Deriv(iter of int, str) -> an int before a str acts as a multiplier 

38  

39 Deriv(Deriv) -> pass through 

40  

41 Deriv(None) -> Deriv(0) 

42  

43 Example: Deriv(['a', 'b', 'b', 'c']) is equivalent to 

44 Deriv(['a', 2, 'b', 'c']). 

45  

46 Raises 

47 ------ 

48 TypeError 

49 If ``*args`` is not of the specified form. 

50 ValueError 

51 If ``*args`` ends with an integer or if there are consecutive 

52 integers. 

53  

54 Attributes 

55 ---------- 

56 implicit 

57 order 

58 max 

59 

60 """ 

61 

62 def __new__(cls, *args): 1efabcd

63 c = collections.Counter() 1efabcd

64 if len(args) == 1: 1efabcd

65 arg = args[0] 1efabcd

66 if isinstance(arg, cls): 1efabcd

67 return arg 1efabcd

68 elif isinstance(arg, (int, np.integer)): 1efabcd

69 if arg < 0: 1efabcd

70 raise ValueError(f'degree {arg} is negative') 1abcd

71 if arg: 1efabcd

72 c.update({None: arg}) 1efabcd

73 elif isinstance(arg, str): 1efabcd

74 c.update([arg]) 1efabcd

75 elif np.iterable(arg): 1efabcd

76 integer = None 1efabcd

77 for obj in arg: 1efabcd

78 if isinstance(obj, str): 1efabcd

79 if integer is not None: 1efabcd

80 if integer: 1efabcd

81 c.update({obj: integer}) 1efabcd

82 integer = None 1efabcd

83 else: 

84 c.update([obj]) 1eabcd

85 elif isinstance(obj, (int, np.integer)): 1efabcd

86 assert obj >= 0, obj 1efabcd

87 if integer is not None: 1efabcd

88 raise ValueError('consecutive integers in iterable') 1abcd

89 integer = int(obj) 1efabcd

90 else: 

91 raise TypeError('objects in iterable must be int or str') 1abcd

92 if integer is not None: 1efabcd

93 raise ValueError('dangling derivative order') 1abcd

94 elif arg is not None: 1efabcd

95 raise TypeError('argument must be None, int, str, or iterable') 1abcd

96 elif len(args) != 0: 1abcd

97 raise ValueError(len(args)) 1abcd

98 assert all(c.values()) 1efabcd

99 self = super().__new__(cls) 1efabcd

100 self._counter = c 1efabcd

101 return self 1efabcd

102 

103 def __getitem__(self, key): 1efabcd

104 return self._counter[key] 1efabcd

105 

106 def __iter__(self): 1efabcd

107 return iter(self._counter) 1efabcd

108 

109 def __len__(self): 1efabcd

110 return len(self._counter) 1abcd

111 

112 def __bool__(self): 1efabcd

113 return bool(self._counter) 1efabcd

114 

115 def __eq__(self, val): 1efabcd

116 if isinstance(val, Deriv): 116 ↛ 117line 116 didn't jump to line 117 because the condition on line 116 was never true1abcd

117 return self._counter == val._counter 

118 else: 

119 return NotImplemented 1abcd

120 

121 def __repr__(self): 1efabcd

122 return dict.__repr__(self._counter) 1abcd

123 

124 @property 1efabcd

125 def implicit(self): 1efabcd

126 """ 

127 True if the derivative is trivial or the variable is implicit. 

128 """ 

129 return not self or next(iter(self._counter)) is None 1efabcd

130 

131 @property 1efabcd

132 def order(self): 1efabcd

133 """ 

134 The total derivation order, i.e., the sum of the values. 

135 """ 

136 # return self._counter.total() # works only in Python >=3.10 

137 return sum(self._counter.values()) 1efabcd

138 

139 @property 1efabcd

140 def max(self): 1efabcd

141 """ 

142 The maximum derivation order for any single variable. 

143 """ 

144 return max(self._counter.values(), default=0)