Coverage for /home/runner/work/AutoDiff/AutoDiff/autodiff/node.py: 100%
Generated by Amelia Li for AutoDiff. (GitHub Profile)
92 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-07 04:22 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-07 04:22 +0000
1import numpy as np
3class Node:
4 """Node implementation for reversed mode."""
6 _supported_scalars = (int, float)
8 def __init__(self, val, gradients=()) -> None:
9 """
10 Initialize a node with its value and local gradients.
12 Parameters
13 ----------
14 val : integer or float
15 Value of a node.
17 gradients : tuple
18 Local gradients of a node.
19 Consists of 1 or 2 tuples of (`child node`, `local gradient value`)
21 """
22 self.val = val
23 self.gradients = gradients
25 ### Elementary Functions ###
26 def __add__(self, other):
27 """
28 Compute addition of two nodes or a node and a constant.
30 Parameters
31 ----------
32 other : Node, constant
33 Input object which is added to a node.
35 Returns
36 -------
37 node
38 The method returns a new node initialized with its value and gradients resulting from the addition.
40 Raises
41 ------
42 TypeError
43 This method raises a `TypeError` if the type of input number other is not supported.
45 """
46 # check if other is of a supported type
47 if not isinstance(other, (*self._supported_scalars, Node)):
48 raise TypeError(f"Unsupported type '{type(other)}'")
50 if isinstance(other, self._supported_scalars):
51 # scalar
52 return Node(other + self.val, ((self, 1),))
53 else:
54 # node
55 return Node(self.val + other.val, ((self, 1),(other, 1)))
57 def __neg__(self):
58 """
59 Compute the negation of one node.
61 Returns
62 -------
63 node
64 The method returns a new node initialized with its value and gradients resulting from the negation.
66 """
67 return Node(-self.val, ((self, -1),))
69 def __radd__(self, other):
70 """
71 Compute addition of two constants.
73 Parameters
74 ----------
75 other : Constant
76 Input constant which is added to another constant.
78 Returns
79 -------
80 node
81 The method returns a new node initialized with its value and gradients resulting from the addition.
83 """
84 return self.__add__(other)
86 def __sub__(self, other):
87 """
88 Compute subtraction of one node from another node or a constant from a node.
91 Parameters
92 ----------
93 other : Node, Constant
94 Input object which is subtracted from a node.
96 Returns
97 -------
98 node
99 The method returns a new node initialized with its value and gradients resulting from the subtraction.
101 """
102 # check if other is of a supported type
103 if not isinstance(other, (*self._supported_scalars, Node)):
104 raise TypeError(f"Unsupported type '{type(other)}'")
106 if isinstance(other, self._supported_scalars):
107 # scalar
108 return Node(self.val - other, ((self, 1),))
109 else:
110 # node
111 return Node(self.val - other.val, ((self, 1),(other, -1)))
114 def __rsub__(self, other):
115 """
116 Compute subtraction of one constant from another constant.
118 Parameters
119 ----------
120 other : Node, Constant
121 Input object which is subtracted by a node.
123 Returns
124 -------
125 node
126 The method returns a new node initialized with its value and gradients resulting from the subtraction.
128 """
129 # check if other is of a supported type
130 if not isinstance(other, (*self._supported_scalars, Node)):
131 raise TypeError(f"Unsupported type '{type(other)}'")
133 return Node(other - self.val, ((self, -1),))
135 def __mul__(self, other):
136 """
137 Compute multiplication of two nodes or a node and a constant.
139 Parameters
140 ----------
141 other : Node, Constant
142 Input object which is multiplied by a node.
144 Returns
145 -------
146 node
147 The method returns a new node initialized with its value and gradients resulting from the multiplication.
149 Raises
150 ------
151 TypeError
152 This method raises a `TypeError` if the type of input number other is not supported.
154 """
155 # check if other is of a supported type
156 if not isinstance(other, (*self._supported_scalars, Node)):
157 raise TypeError(f"Unsupported type '{type(other)}'")
158 if isinstance(other, self._supported_scalars):
159 # scalar
160 return Node(other * self.val, ((self, other),))
161 else:
162 # node
163 return Node(self.val * other.val, ((self, other.val),(other, self.val)))
165 def __rmul__(self, other):
166 """
167 Compute multiplication of two constants.
169 Parameters
170 ----------
171 other : Constant
172 Input constant which is multiplied by another constant.
174 Returns
175 -------
176 node
177 The method returns a new node initialized with its value and gradients resulting from the multiplication.
179 """
180 return self.__mul__(other)
182 def __truediv__(self, other):
183 """
184 Compute division of 'self' node by 'other' node.
186 Parameters
187 ----------
188 other : Node, Constant
189 Input object which divides a node.
191 Returns
192 -------
193 node
194 The method returns a new node initialized with its value and gradients resulting from the division.
196 """
197 # check if other is of a supported type
198 if not isinstance(other, (*self._supported_scalars, Node)):
199 raise TypeError(f"Unsupported type '{type(other)}'")
200 if isinstance(other, self._supported_scalars):
201 # scalar
202 return Node(self.val/other, ((self, 1/other),))
203 else:
204 # node
205 return Node(self.val/other.val, ((self, 1/other.val),(other, -self.val*other.val**-2)))
207 def __rtruediv__(self, other):
208 """
209 Compute division of 'other' node by 'self' node.
211 Parameters
212 ----------
213 other : Node, Constant
214 Input object which is divided by a node.
216 Returns
217 -------
218 node
219 The method returns a new node initialized with its value and gradients resulting from the division.
221 """
222 # check if other is of a supported type
223 if not isinstance(other, (*self._supported_scalars, Node)):
224 raise TypeError(f"Unsupported type '{type(other)}'")
225 return Node(other/self.val, ((self, -other*self.val**-2),))
227 def __pow__(self, other):
228 """
229 Compute the exponentiation of raising one node value to the power of another node value or of one constant.
231 Parameters
232 ----------
233 other : Node, Constant
234 Input object to whose power the node will be raised.
236 Returns
237 -------
238 node
239 The method returns a new node initialized with its value and gradients resulting from the exponentiation.
241 Raises
242 ------
243 TypeError
244 This method raises a `TypeError` if the type of input number other is not supported.
246 """
247 # check if other is of supported type
248 if not isinstance(other, (*self._supported_scalars, Node)):
249 raise TypeError(f"Unsupported type '{type(other)}'")
250 if isinstance(other, self._supported_scalars):
251 # scalar
252 return Node(self.val**other, ((self, other*self.val**(other-1)),))
253 else:
254 # node
255 return Node(self.val**other.val, ((self, other.val*self.val**(other.val-1)),
256 (other, self.val**other.val*np.log(self.val))))
258 def __rpow__(self, other):
259 """
260 Compute the exponentiation of raising 'other' node value to the power of 'self' node value.
262 Parameters
263 ----------
264 other : Node, Constant
265 Input object which will be raised to the power of the 'self' node value.
267 Returns
268 -------
269 node
270 The method returns a new node initialized with its value and gradients resulting from the exponentiation.
272 Raises
273 ------
274 TypeError
275 This method raises a `TypeError` if the type of input number other is not supported.
277 """
278 # check if other is of supported type
279 if not isinstance(other, (*self._supported_scalars, Node)):
280 raise TypeError(f"Unsupported type '{type(other)}'")
281 return Node(other**self.val, ((self, other**self.val*np.log(other)),))
283 ### Square Root Function ###
284 def sqrt(self):
285 """
286 Compute the square root of one node.
288 Returns
289 -------
290 Dual
291 The method returns a new node initialized with its value and gradients resulting from the square root.
293 Raises
294 ------
295 ValueError
296 This method raises a `ValueError` if the value of input node value is less than zero.
298 """
299 if self.val < 0:
300 raise ValueError("Cannot square root: value of node is less than 0.")
301 return Node(self.val ** (1/2), ((self, 1/2*(self.val**(-1/2))),))
303 ### Exponential Function ###
304 def exp(self):
305 """
306 Compute the exponentiation of raising the natural number to the power of a node value.
308 Returns
309 -------
310 Node
311 The method returns a new node initialized with its value and gradients resulting from the exponentiation.
313 """
314 return Node(np.exp(self.val), ((self, np.exp(self.val)),))
316 ### Logarithmic Function ###
317 def log(self, base):
318 """
319 Compute the logarithm to find the power to which the input base must be raised to yield the given node value.
321 Parameters
322 ----------
323 base : Node, Constant
324 Input base which is raised to yield a given node value.
326 Returns
327 -------
328 Node
329 The method returns a new node initialized with its value and gradients resulting from the logarithm.
331 Raises
332 ------
333 TypeError
334 This method raises a `TypeError` if the type of input base number is not supported.
336 ValueError
337 This method raises a `ValueError` if the value of node or the value of input base is less than zero.
339 """
340 # check if base is of supported type
341 if not isinstance(base, self._supported_scalars):
342 raise TypeError(f"Unsupported base type '{type(base)}'")
343 # check that the base is above 0.
344 if base <= 0:
345 raise ValueError("Cannot log: Base is less than or equal to 0.")
346 # check that the value of the node is greater than 0.
347 if self.val <= 0:
348 raise ValueError("Cannot log: Value of node is less than or equal to 0.")
349 return Node(np.log(self.val)/np.log(base), ((self, 1 / (np.log(base)*self.val)),))
351 ### Logistic Function ###
352 def standard_logistic(self):
353 """
354 Compute the value of the standard logistic function with the given node as input.
356 Returns
357 -------
358 node
359 The method returns the value of the standard logistic function with the given node as input.
361 """
362 return Node(1 / (1 + np.exp(-self.val)),((self, 1/(1+np.exp(-self.val)) * (1-1/(1+np.exp(-self.val)))),))
364 ### Trigonometric Functions ###
365 def sin(self):
366 """
367 Compute the sine of a node value.
369 Returns
370 -------
371 Node
372 The method returns The method returns a new node initialized with its value and gradients resulting from the sine.
374 """
375 return Node(np.sin(self.val), ((self, np.cos(self.val)),))
377 def cos(self):
378 """
379 Compute the cosine of a node value.
381 Returns
382 -------
383 Node
384 The method returns a new node initialized with its value and gradients resulting from the cosine.
386 """
387 return Node(np.cos(self.val), ((self, -np.sin(self.val)),))
389 def tan(self):
390 """
391 Compute the tangent of a node value.
393 Returns
394 -------
395 Node
396 The method returns a new node initialized with its value and gradients resulting from the tangent.
398 """
399 return Node(np.tan(self.val), ((self, 1 / (np.cos(self.val) ** 2)),))
401 ### Inverse Trigonometric Functions ###
402 def arcsin(self):
403 """
404 Compute the arcsine of a node value.
406 Returns
407 -------
408 Node
409 The method returns a new node initialized with its value and gradients resulting from the arcsine.
411 Raises
412 ------
413 ValueError
414 This method raises a `ValueError` if the value of the node is smaller than -1 or greater than 1.
416 """
417 if self.val >= 1 or self.val <= -1:
418 raise ValueError("Value of node is not between -1 and 1.")
419 return Node(np.arcsin(self.val), ((self, 1 / np.sqrt(1 - self.val ** 2)),))
421 def arccos(self):
422 """
423 Compute the arccosine of a node value.
425 Returns
426 -------
427 Node
428 The method returns a new node initialized with its value and gradients resulting from the arccosine.
430 Raises
431 ------
432 ValueError
433 This method raises a `ValueError` if the value of the node is smaller than -1 or greater than 1.
435 """
436 if self.val >= 1 or self.val <= -1:
437 raise ValueError("Value of node is not between -1 and 1.")
438 return Node(np.arccos(self.val), ((self, - 1 / np.sqrt(1 - self.val ** 2)),))
440 def arctan(self):
441 """
442 Compute the arctangent of a node value.
444 Returns
445 -------
446 Node
447 The method returns a new node initialized with its value and gradients resulting from the arctangent.
449 """
450 return Node(np.arctan(self.val), ((self, 1 / ((self.val ** 2) + 1)),))
452 ### Hyperbolic Functions ###
453 def sinh(self):
454 """
455 Compute the hyperbolic sine of a node value.
457 Returns
458 -------
459 Node
460 The method returns a new node initialized with its value and gradients resulting from the hyperbolic sine.
462 """
463 return Node(np.sinh(self.val), ((self, np.cosh(self.val)),))
465 def cosh(self):
466 """
467 Compute the hyperbolic cosine of a node value.
469 Returns
470 -------
471 Node
472 The method returns a new node initialized with its value and gradients resulting from the hyperbolic cosine.
474 """
475 return Node(np.cosh(self.val), ((self, np.sinh(self.val)),))
477 def tanh(self):
478 """
479 Compute the hyperbolic tangent of a node value.
481 Returns
482 -------
483 Node
484 The method returns a new node initialized with its value and gradients resulting from the hyperbolic tangent.
486 """
487 return Node(np.tanh(self.val), ((self, 1/np.cosh(self.val)**2),))