Coverage for autodiff_team29/elementaries.py: 99%
159 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-15 21:37 +0000
« prev ^ index » next coverage.py v7.2.3, created at 2023-04-15 21:37 +0000
1from typing import Union
2import numpy as np
3import math
4from autodiff_team29 import Node
7def _check_log_domain_restrictions(x: Node) -> None:
8 """
9 Checks if the value of a given input x is less than or equal to zero and therefore
10 unable to be used as an input for a logrithmic function.
12 Parameters
13 ----------
14 x: Node
16 Returns
17 -------
18 Returns None
19 if x > 0
21 Raises
22 ------
23 ValueError
24 if x <= 0.
26 Examples
27 --------
28 >>> _check_log_domain_restrictions(Node("1",1,0))
29 None
30 >>> _check_log_domain_restrictions(Node("0",0,0))
31 ValueError: Value 0 not valid for a logarithmic function
32 >>> _check_log_domain_restrictions(Node("-1",-1,0))
33 ValueError: Value '-1' not valid for a logarithmic functionNone
35 """
36 if x.value <= 0:
37 raise ValueError(f"Value '{x.value} 'not valid for a logarithmic function")
40def _check_sqrt_domain_restrictions(x: Node) -> None:
41 """
42 Checks if the value of a given input x is less zero and therefore
43 unable to be used as an input for a square root function.
45 Parameters
46 ----------
47 x : Node
49 Returns
50 -------
51 None
52 if x >= 0
54 Raises
55 ------
56 ValueError
57 if x < 0.
59 Examples
60 --------
61 >>> _check_sqrt_domain_restrictions(Node("1",1,0))
62 None
63 >>> _check_sqrt_domain_restrictions(Node("0",0,0))
64 None
65 >>> _check_sqrt_domain_restrictions(Node("-1",-1,0))
66 ValueError: Square roots of negative numbers not supported
68 """
69 if x.value < 0:
70 raise ValueError("Square roots of negative numbers not supported")
73def _check_tan_domain_restrictions(x: Node) -> None:
74 """
75 Checks if cosine of given value is zero and thus invalid for tangent.
77 Parameters
78 ----------
79 x : Node
81 Returns
82 -------
83 None
84 if cos(x) != 0
86 Raises
87 ------
88 ValueError
89 if cos(x) == 0.
91 Examples
92 --------
93 >>> _check_tan_domain_restrictions(Node("1",1,0))
94 None
95 >>> _check_tan_domain_restrictions(Node("0",0,0))
96 None
97 >>> _check_tan_domain_restrictions(Node("pi",np.pi/2,0))
98 ValueError: Value, pi/2, not within domain of tan
100 """
101 if np.cos(x.value) == 0:
102 raise ValueError(f"Value, {x.value}, not within domain of tan")
105def _check_arccos_domain_restrictions(x: Node) -> None:
106 """
107 Checks if the value of a given input x is not -1 ≤ x ≤ 1 therefore
108 unable to be used as an input for the arccos function.
110 Parameters
111 ----------
112 x : Node
114 Returns
115 -------
116 None
117 if -1 ≤ x ≤ 1
119 Raises
120 ------
121 ValueError
122 if |x| > 1.
124 Examples
125 --------
126 >>> _check_arccos_domain_restrictions(Node("1",1,0))
127 None
128 >>> _check_arccos_domain_restrictions(Node("0",0,0))
129 None
130 >>> _check_arccos_domain_restrictions(Node("-5",-1,0))
131 ValueError: '-5' is not within the domain [-1,1] of f(x)=arccos(x)
133 """
134 if np.abs(x.value) > 1:
135 raise ValueError(
136 f"'{x.value}' is not within the domain [-1,1] of f(x)=arccos(x)"
137 )
140def _check_arcsin_domain_restrictions(x: Node) -> None:
141 """
142 Checks if the value of a given input x is not -1 ≤ x ≤ 1 and therefore
143 unable to be used as an input for the arcsin function.
145 Parameters
146 ----------
147 x : Node
149 Returns
150 -------
151 None
152 if -1 ≤ x ≤ 1
154 Raises
155 ------
156 ValueError
157 if x < 0.
159 Examples
160 --------
161 >>> _check_arcsin_domain_restrictions(Node("1",1,0))
162 None
163 >>> _check_arcsin_domain_restrictions(Node("0",0,0))
164 None
165 >>> _check_arcsin_domain_restrictions(Node("-5",-1,0))
166 ValueError: '-5' is not within the domain [-1,1] of f(x)=arcsin(x)
167 """
168 if np.abs(x.value) > 1:
169 raise ValueError(f"{x.value} is not within the domain [-1,1] of f(x)=arcsin(x)")
172def sqrt(x: Union[int, float, Node]) -> Node:
173 """
174 Takes in an instance of the Node class and returns a new node with its symbolic
175 representation, forward trace, and tangent trace, which are based of the square
176 root of the input node x.
178 Parameters
179 ----------
180 x : Union[int, float, Node]
182 Returns
183 -------
184 Node
186 Examples
187 --------
188 >>> sqrt(Node("1",1,0))
189 Node("sqrt(1)", 1, 0)
190 >>> sqrt(Node("0",0,0))
191 Node("sqrt(0)", 0, 0)
192 >>> sqrt(Node("-1",-1,0))
193 ValueError: Square roots of negative numbers not supported
195 """
196 symbolic_representation = "sqrt({})".format(str(x))
197 if Node._check_node_exists(symbolic_representation):
198 return Node._get_existing_node(symbolic_representation)
200 x = Node._convert_numeric_type_to_node(x)
202 _check_sqrt_domain_restrictions(x)
204 forward_trace = np.sqrt(x.value)
205 tangent_trace = x.derivative / (2 * np.sqrt(x.value))
206 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
208 return new_node
211def ln(x: Union[int, float, Node]) -> Node:
212 """
213 Takes in an instance of the Node class and returns a new node with its symbolic
214 representation, forward trace, and tangent trace, which are based on the natural log
215 of the input node x.
217 Parameters
218 ----------
219 x : Union[int, float, Node]
221 Returns
222 -------
223 Node
225 Examples
226 --------
227 >>> ln(Node("1",1,0))
228 Node("ln(1)", 0, 1)
229 >>> ln(Node("0",0,0))
230 ValueError: Value 0 not valid for a logarithmic function
231 >>> ln(-1)
232 ValueError: Value '-1' not valid for a logarithmic functionNone
234 """
235 symbolic_representation = "ln({})".format(str(x))
236 if Node._check_node_exists(symbolic_representation):
237 return Node._get_existing_node(symbolic_representation)
239 x = Node._convert_numeric_type_to_node(x)
241 _check_log_domain_restrictions(x)
243 forward_trace = np.log(x.value)
244 tangent_trace = 1 / x.value
245 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
247 return new_node
250def log(x: Union[int, float, Node], base: Union[int, float, Node] = np.e) -> Node:
251 """
252 Takes in an instance of the Node class and returns a new node with its symbolic
253 representation, forward trace, and tangent trace, which are based on the logarithm
254 of the input node x and the provided base.
256 Parameters
257 ----------
258 x : Union[int, float, Node]
260 base : Union[int, float]
261 The desired base of the logorithm. Must be an integer or float greater than 1.
263 Returns
264 -------
265 Node
267 Examples
268 --------
269 >>> log(Node("1",1,0), 10)
270 Node("log10(1)", 0, 0.4343)
271 >>> log(Node("1",1,0), 2)
272 Node("log2(1)", 0, 1.4427)
273 >>> log(Node("0",0,0))
274 ValueError: Value 0 not valid for a logarithmic function
275 >>> log(Node("-1",-1,0))
276 ValueError: Value -1 not valid for a logarithmic function
278 """
279 if not base > 1:
280 raise ValueError("Base must be greater than 1")
282 symbolic_representation = f"log{str(base)}({str(x)})"
283 if Node._check_node_exists(symbolic_representation):
284 return Node._get_existing_node(symbolic_representation)
286 x = Node._convert_numeric_type_to_node(x)
288 _check_log_domain_restrictions(x)
290 forward_trace = math.log(x.value, base)
291 tangent_trace = 1 / (x.value * np.log(base))
292 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
294 return new_node
297def exp(x: Union[int, float, Node]) -> Node:
298 """
299 Takes in an instance of the Node class and returns a new node with its symbolic
300 representation, forward trace, and tangent trace, which are based on the exponential
301 value of the input node x.
303 Parameters
304 ----------
305 x : Union[int, float, Node]
307 Returns
308 -------
309 Node
311 Examples
312 --------
313 >>> exp(Node("1",1,0))
314 Node("exp(1)", 2.7183, 0)
315 >>> exp(Node("0",0,0))
316 Node("exp(0)", 1, 0)
317 >>> exp(Node("-1",-1,0))
318 Node("exp(-1)", 0.3679, 0)
320 """
321 symbolic_representation = "exp({})".format(str(x))
322 if Node._check_node_exists(symbolic_representation):
323 return Node._get_existing_node(symbolic_representation)
325 x = Node._convert_numeric_type_to_node(x)
327 forward_trace = np.exp(x.value)
328 tangent_trace = x.derivative * forward_trace
329 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
331 return new_node
334def sin(x: Union[int, float, Node]) -> Node:
335 """
336 Takes in an instance of the Node class and returns a new node with its symbolic
337 representation, forward trace, and tangent trace, which are based on the sine
338 of the input node x.
340 Parameters
341 ----------
342 x : Union[int, float, Node]
344 Returns
345 -------
346 Node
348 Examples
349 --------
350 >>> sin(Node("1",1,0))
351 Node("sin(1)", 0.8415, 0)
352 >>> sin(Node("0",0,0))
353 Node("sin(0)", 0, 0)
354 >>> sin(Node("-1",-1,0))
355 Node("sin(-1)", -0.8415, 0)
357 """
358 symbolic_representation = "sin({})".format(str(x))
359 if Node._check_node_exists(symbolic_representation):
360 return Node._get_existing_node(symbolic_representation)
362 x = Node._convert_numeric_type_to_node(x)
364 forward_trace = np.sin(x.value)
365 tangent_trace = np.cos(x.value) * x.derivative
366 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
368 return new_node
371def cos(x: Union[int, float, Node]) -> Node:
372 """
373 Takes in an instance of the Node class and returns a new node with its symbolic
374 representation, forward trace, and tangent trace, which are based on the cosine
375 of the input node x.
377 Parameters
378 ----------
379 x : Union[int, float, Node]
381 Returns
382 -------
383 Node
385 Examples
386 --------
387 >>> cos(Node("1",1,0))
388 Node("cos(1)", 0.5403, 0)
389 >>> cos(Node("0",1,0))
390 Node("cos(0)", 1, 0)
391 >>> cos(Node("-1",-1,0))
392 Node("cos(-1)", -0.5403, 0)
394 """
395 symbolic_representation = "cos({})".format(str(x))
396 if Node._check_node_exists(symbolic_representation):
397 return Node._get_existing_node(symbolic_representation)
399 x = Node._convert_numeric_type_to_node(x)
401 forward_trace = np.cos(x.value)
402 tangent_trace = -np.sin(x.value) * x.derivative
403 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
405 return new_node
408def tan(x: Union[int, float, Node]) -> Node:
409 """
410 Takes in an instance of the Node class and returns a new node with its symbolic
411 representation, forward trace, and tangent trace, which are based on the tangent
412 of the input node x.
414 Parameters
415 ----------
416 x : Union[int, float, Node]
418 Returns
419 -------
420 Node
422 Examples
423 --------
424 >>> tan(Node("1",1,0))
425 Node("tan(1)", 1.557, 0)
426 >>> tan(Node("0",0,0))
427 Node("tan(0)", 0, 0)
428 >>> tan(Node("-1",-1,0))
429 Node("tan(-1)", -1.557, 0)
431 """
432 symbolic_representation = "tan({})".format(str(x))
433 if Node._check_node_exists(symbolic_representation):
434 return Node._get_existing_node(symbolic_representation)
436 x = Node._convert_numeric_type_to_node(x)
438 _check_tan_domain_restrictions(x)
440 forward_trace = np.tan(x.value)
441 tangent_trace = x.derivative / (np.cos(x.value) ** 2)
442 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
444 return new_node
447def arcsin(x: Union[int, float, Node]) -> Node:
448 """
449 Takes in an instance of the Node class and returns a new node with its symbolic
450 representation, forward trace, and tangent trace, which are based on the arcsin
451 of the input node x.
453 Parameters
454 ----------
455 x : Union[int, float, Node]
457 Returns
458 -------
459 Node
461 Examples
462 --------
463 >>> arcsin(Node("1",1,0))
464 Node("arcsin(1)", 1.5708, 0)
465 >>> arcsin(Node("0",0,0))
466 Node("arcsin(0)", 0, 0)
467 >>> arcsin(Node("-1",-1,0))
468 Node("arcsin(-1)", -1.5708, 0)
470 """
471 symbolic_representation = "arcsin({})".format(str(x))
472 if Node._check_node_exists(symbolic_representation):
473 return Node._get_existing_node(symbolic_representation)
475 x = Node._convert_numeric_type_to_node(x)
477 _check_arcsin_domain_restrictions(x)
479 forward_trace = np.arcsin(x.value)
480 tangent_trace = x.derivative / np.sqrt(1 - x.value ** 2)
481 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
483 return new_node
486def arccos(x: Union[int, float, Node]) -> Node:
487 """
488 Takes in an instance of the Node class and returns a new node with its symbolic
489 representation, forward trace, and tangent trace, which are based on the arccos
490 of the input node x.
492 Parameters
493 ----------
494 x : Union[int, float, Node]
496 Returns
497 -------
498 Node
500 Examples
501 --------
502 >>> arccos(Node("1",1,0))
503 Node("arccos(1)", 3.1416, 0)
504 >>> arccos(Node("0",0,0))
505 Node("arccos(0)", 1.5708, 0)
506 >>> arccos(Node("-1",-1,0))
507 Node("arccos(-1)", -3.1416, 0)
509 """
510 symbolic_representation = "arccos({})".format(str(x))
511 if Node._check_node_exists(symbolic_representation):
512 return Node._get_existing_node(symbolic_representation)
514 x = Node._convert_numeric_type_to_node(x)
516 _check_arccos_domain_restrictions(x)
518 forward_trace = np.arccos(x.value)
519 tangent_trace = -x.derivative / np.sqrt(1 - x.value ** 2)
520 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
522 return new_node
525def arctan(x: Union[int, float, Node]) -> Node:
526 """
527 Takes in an instance of the Node class and returns a new node with its symbolic
528 representation, forward trace, and tangent trace, which are based on the arctan
529 of the input node x.
531 Parameters
532 ----------
533 x : Union[int, float, Node]
535 Returns
536 -------
537 Node
539 Examples
540 --------
541 >>> arctan(Node("1",1,0))
542 Node("arctan(1)", 0.7854, 0)
543 >>> arctan(Node("0",0,0))
544 Node("arctan(0)", 0, 0)
545 >>> arctan(Node("-1",-1,0))
546 Node("arctan(-1)", -0.7854, 0)
548 """
549 symbolic_representation = "arctan({})".format(str(x))
550 if Node._check_node_exists(symbolic_representation):
551 return Node._get_existing_node(symbolic_representation)
553 x = Node._convert_numeric_type_to_node(x)
555 forward_trace = np.arctan(x.value)
556 tangent_trace = x.derivative / (1 + x.value ** 2)
557 new_node = Node(
558 symbolic_representation,
559 forward_trace,
560 tangent_trace,
561 )
563 return new_node
566def power(base: Union[int, float, Node], exponent: Union[int, float, Node]) -> Node:
567 """
568 Takes in an instance of the Node class and returns a new node with its symbolic
569 representation, forward trace, and tangent trace, which are based on the power
570 of the input node x.
572 Parameters
573 ----------
574 base : Union[int, float, Node]
575 exponent : Union[int, float, Node]
577 Returns
578 -------
579 Node
581 Examples
582 --------
583 >>> power(3,2)
584 Node("3**2", 9, 0)
586 """
587 symbolic_representation = f"({base}**{exponent})"
588 if Node._check_node_exists(symbolic_representation):
589 return Node._get_existing_node(symbolic_representation)
591 base = Node._convert_numeric_type_to_node(base)
593 return base ** exponent
596def sinh(x: Union[int, float, Node]) -> Node:
597 """
598 Takes in an instance of the Node class and returns a new node with its symbolic
599 representation, forward trace, and tangent trace, which are based on the sinh
600 of the input node x.
602 Parameters
603 ----------
604 x : Union[int, float, Node]
606 Returns
607 -------
608 Node
611 Examples
612 --------
613 >>> sinh(1)
614 Node("sinh(1)", 1.1752011936438014, 0)
616 """
617 symbolic_representation = f"sinh({x})"
618 if Node._check_node_exists(symbolic_representation):
619 return Node._get_existing_node(symbolic_representation)
621 x = Node._convert_numeric_type_to_node(x)
623 forward_trace = np.sinh(x.value)
624 tangent_trace = np.cosh(x.value) * x.derivative
625 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
627 return new_node
630def cosh(x: Union[int, float, Node]) -> Node:
631 """
632 Takes in an instance of the Node class and returns a new node with its symbolic
633 representation, forward trace, and tangent trace, which are based on the sinh
634 of the input node x.
636 Parameters
637 ----------
638 x : Union[int, float, Node]
640 Returns
641 -------
642 Node
644 Examples
645 --------
646 >>> cosh(1)
647 Node("cosh(1)", 1.5430806348152437, 0)
649 """
650 symbolic_representation = f"cosh({x})"
651 if Node._check_node_exists(symbolic_representation):
652 return Node._get_existing_node(symbolic_representation)
654 x = Node._convert_numeric_type_to_node(x)
656 forward_trace = np.cosh(x.value)
657 tangent_trace = np.sinh(x.value) * x.derivative
658 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
660 return new_node
663def tanh(x: Union[int, float, Node]) -> Node:
664 """
665 Takes in an instance of the Node class and returns a new node with its symbolic
666 representation, forward trace, and tangent trace, which are based on the
667 of the input node x.
669 Parameters
670 ----------
671 x : Union[int, float, Node]
673 Returns
674 -------
675 Node
678 Examples
679 --------
680 >>> tanh(1)
681 Node("tanh(1)", 0.76159415595, 0)
683 """
684 symbolic_representation = f"tanh({x})"
685 if Node._check_node_exists(symbolic_representation):
686 return Node._get_existing_node(symbolic_representation)
688 x = Node._convert_numeric_type_to_node(x)
690 forward_trace = np.tanh(x.value)
691 tangent_trace = (1 - np.tanh(x.value) ** 2) * x.derivative
692 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
694 return new_node
697def logistic(x: Union[int, float, Node]) -> Node:
698 """
699 Takes in an instance of the Node class and returns a new node with its symbolic
700 representation, forward trace, and tangent trace, which are based on the
701 of the input node x.
703 Parameters
704 ----------
705 x : Union[int, float, Node]
707 Returns
708 -------
709 Node
711 Examples
712 --------
713 >>> logistic(1)
714 Node("logistic(1)", 1.1752011936438014, 0)
716 """
717 symbolic_representation = f"logistic({x})"
718 if Node._check_node_exists(symbolic_representation):
719 return Node._get_existing_node(symbolic_representation)
721 x = Node._convert_numeric_type_to_node(x)
723 forward_trace = np.exp(-np.logaddexp(0, -x.value))
724 tangent_trace = (
725 (np.exp(-np.logaddexp(0, -x.value)))
726 * (1 - np.exp(-np.logaddexp(0, -x.value)))
727 * x.derivative
728 )
729 new_node = Node(symbolic_representation, forward_trace, tangent_trace)
731 return new_node