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
« 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/>.
20import collections 1efabcd
22import numpy as np 1efabcd
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.
31 Deriv(int) -> specified order derivative
33 Deriv(str) -> first derivative w.r.t. specified variable
35 Deriv(iter of str) -> derivative w.r.t. specified variables
37 Deriv(iter of int, str) -> an int before a str acts as a multiplier
39 Deriv(Deriv) -> pass through
41 Deriv(None) -> Deriv(0)
43 Example: Deriv(['a', 'b', 'b', 'c']) is equivalent to
44 Deriv(['a', 2, 'b', 'c']).
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.
54 Attributes
55 ----------
56 implicit
57 order
58 max
60 """
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
103 def __getitem__(self, key): 1efabcd
104 return self._counter[key] 1efabcd
106 def __iter__(self): 1efabcd
107 return iter(self._counter) 1efabcd
109 def __len__(self): 1efabcd
110 return len(self._counter) 1abcd
112 def __bool__(self): 1efabcd
113 return bool(self._counter) 1efabcd
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
121 def __repr__(self): 1efabcd
122 return dict.__repr__(self._counter) 1abcd
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
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
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)