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

1from __future__ import annotations 

2 

3import numpy as np 

4 

5 

6class DualNumber: 

7 """Tuple-like object for storing the value and directional derivatives during AD passes 

8 

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. 

12 

13 NOTE: Code in this module is taken from peer programming exercise 7 

14 

15 >>> foo = DualNumber() 

16 >>> bar = DualNumber() 

17 >>> foo * bar 

18 """ 

19 

20 def __init__(self, real : float | int, dual: float = 1.0): 

21 self.real = real 

22 self.dual = dual 

23 

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.") 

31 

32 def __radd__(self, other: int | float) -> DualNumber: 

33 """Add a dual number to a scalar""" 

34 return self.__add__(other) 

35 

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.") 

43 

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) 

50 

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.") 

58 

59 def __rmul__(self, other: int | float) -> DualNumber: 

60 """Multiply a scalar by a dual number""" 

61 return self.__mul__(other) 

62 

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.") 

70 

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.") 

76 

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.") 

84 

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.") 

90 

91 def __neg__(self: DualNumber) -> DualNumber: 

92 """Negate a dual number""" 

93 return -1 * self 

94 

95 

96if __name__ == "__main__": 

97 z1 = DualNumber(2,3) 

98 

99 r = 3 

100 

101 print((r * z1).real) 

102 print((r * z1).dual) 

103 

104 print((z1 * r).real) 

105 print((z1 * r).dual) 

106 

107