Coverage for Adifpy/differentiate/dual_number.py: 32%
62 statements
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-22 19:44 -0500
« prev ^ index » next coverage.py v6.5.0, created at 2022-11-22 19:44 -0500
1from __future__ import annotations
3import numpy as np
6class DualNumber:
7 """Tuple-like object for storing the value and directional derivatives during AD passes
9 This module includes our implementation of dual numbers that will be used
10 to calculate function and first-order derivative values at a point specified
11 by the user in our auto-differentiation program.
13 NOTE: Code in this module is taken from peer programming exercise 7
15 >>> foo = DualNumber()
16 >>> bar = DualNumber()
17 >>> foo * bar
18 """
20 def __init__(self, real : float | int, dual: float = 1.0):
21 self.real = real
22 self.dual = dual
24 def __add__(self, other: DualNumber | int | float) -> DualNumber:
25 """Add a scalar or a dual number to dual number"""
26 if isinstance(other, DualNumber):
27 return DualNumber(self.real + other.real, self.dual + other.dual)
28 elif isinstance(other, int) or isinstance(other, float):
29 return DualNumber(self.real + other, self.dual)
30 raise TypeError("Operand must be of type int, float, or DualNumber.")
32 def __radd__(self, other: int | float) -> DualNumber:
33 """Add a dual number to a scalar"""
34 return self.__add__(other)
36 def __sub__(self, other: DualNumber | int | float) -> DualNumber:
37 """Subtract a scalar or a dual number from a dual number"""
38 if isinstance(other, DualNumber):
39 return DualNumber(self.real - other.real, self.dual - other.dual)
40 elif isinstance(other, int) or isinstance(other, float):
41 return DualNumber(self.real - other, self.dual)
42 raise TypeError("Operand must be of type int, float, or DualNumber.")
44 def __rsub__(self, other: int | float) -> DualNumber:
45 """Subtract a dual number from a scalar"""
46 if isinstance(other, DualNumber):
47 return DualNumber(other.real - self.real, other.dual - self.dual)
48 elif isinstance(other, int) or isinstance(other, float):
49 return DualNumber(other - self.real, -self.dual)
51 def __mul__(self, other: DualNumber | int | float) -> DualNumber:
52 """Multiply a dual number by another dual number or a scalar"""
53 if isinstance(other, DualNumber):
54 return DualNumber(self.real * other.real, self.real * other.dual + self.dual * other.real)
55 elif isinstance(other, int) or isinstance(other, float):
56 return DualNumber(self.real * other, self.dual * other)
57 raise TypeError("Operand must be of type int, float, or DualNumber.")
59 def __rmul__(self, other: int | float) -> DualNumber:
60 """Multiply a scalar by a dual number"""
61 return self.__mul__(other)
63 def __truediv__(self, other: DualNumber | int | float) -> DualNumber:
64 """Divide a dual number by another dual number or a scalar"""
65 if isinstance(other, DualNumber):
66 return(DualNumber(self.real / other.real, (self.dual * other.real - other.dual * self.real) / (other.real**2)))
67 elif isinstance(other, int) or isinstance(other, float):
68 return(DualNumber(self.real / other, self.dual / other))
69 raise TypeError("Operand must be of type int, float, or DualNumber.")
71 def __rtruediv__(self, other: int | float) -> DualNumber:
72 """Divide a scalar by a dual number"""
73 if isinstance(other, int) or isinstance(other, float):
74 return(DualNumber(other / self.real, - self.dual * other / pow(self.real,2)))
75 raise TypeError("Operand must be of type int, float, or DualNumber.")
77 def __pow__(self, other: DualNumber | int | float) -> DualNumber:
78 """Raise a dual number to another dual number or a scalar"""
79 if isinstance(other, DualNumber):
80 return(DualNumber(pow(self.real, other.real), other.dual * np.log(self.real) * pow(self.real, other.real) + self.dual * other.real * pow(self.real, other.real - 1)))
81 if isinstance(other, int) or isinstance(other, float):
82 return(DualNumber(pow(self.real, other), self.dual * other * pow(self.real, other - 1)))
83 raise TypeError("Operand must be of type int, float, or DualNumber.")
85 def __rpow__(self, other: int | float) -> DualNumber:
86 """Raise a scalar to a dual number"""
87 if isinstance(other, int) or isinstance(other, float):
88 return(DualNumber(pow(other, self.real), self.dual * np.log(other) * pow(other, self.real)))
89 raise TypeError("Operand must be of type int, float, or DualNumber.")
91 def __neg__(self: DualNumber) -> DualNumber:
92 """Negate a dual number"""
93 return -1 * self
96if __name__ == "__main__":
97 z1 = DualNumber(2,3)
99 r = 3
101 print((r * z1).real)
102 print((r * z1).dual)
104 print((z1 * r).real)
105 print((z1 * r).dual)