Coverage for /home/runner/work/AutoDiff/AutoDiff/autodiff/forwardmode.py: 100%


Generated by Amelia Li for AutoDiff. (GitHub Profile)

53 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-07 04:22 +0000

1# File : forwardmode.py 

2# Description: Forward mode implentation of automatic differentiation that  

3# uses the properties of dual numbers to return the value of 

4# f(x) and f'(x) 

5 

6import inspect 

7import numpy as np 

8 

9from autodiff.ad import AD 

10from autodiff.dual import Dual 

11 

12class ForwardMode(AD): 

13 """Forward mode implementation based on dual number data structure.""" 

14 

15 def get_results(self, x): 

16 """ 

17 Compute the value(s) and the derivative(s) of the function(s) based on input 'x'. 

18 

19 Parameters 

20 ---------- 

21 x : Scalar, Vector.  

22 The point at which the value(s) and derivative(s) of the function(s) are evaluated.  

23 

24 Returns 

25 ------- 

26 f(x) and f'(x) 

27 The method returns both the value(s) and the derivative(s) of the function(s) at 'x'. 

28  

29 Raises 

30 ------ 

31 TypeError 

32 This method raises a `TypeError` if the type of input 'x' is not supported. 

33  

34 ValueError 

35 This method also raises a `ValueError` if the dimension of input 'x' is not matched with the function(s). 

36  

37 """ 

38 reals = [] 

39 duals = [] 

40 

41 # check that x is of supported type 

42 if not isinstance(x, self._supported_vectors): 

43 raise TypeError(f"Unsupported type '{type(x)}'") 

44 

45 # check that x is 1-dimensional 

46 if len(np.shape(x)) != 1: 

47 raise ValueError(f"Input variables should be a 1-dimensional.") 

48 

49 # convert x to a list 

50 x = list(x) 

51 

52 # if there are multiple functions 

53 if self.jacobian: 

54 for f in self.f: 

55 # initialize arguments list 

56 args = [] 

57 fduals = [] 

58 

59 # get the number of arguments 

60 n = len(inspect.getfullargspec(f)[0]) 

61 

62 # if the number of arguments is lesser than the number of inputs 

63 if n < self.n: 

64 # get the function arguments 

65 function_args = inspect.getfullargspec(f)[0] 

66 

67 # if the function argument is an input, add it to the arguments list 

68 for i in self.inputs: 

69 if i in function_args: 

70 args.append(x[self.inputs.index(i)]) 

71 

72 # convert every element in args to a dual number with dual component 0 except for the target 

73 for i in range(len(args)): 

74 target_i = [Dual(arg, 0) for arg in args] 

75 target_i[i] = Dual(args[i]) 

76 z = f(*target_i) 

77 fduals.append(z.dual) 

78 

79 # insert zeros for variables that are not present in the function 

80 for i in self.inputs: 

81 if i not in function_args: 

82 fduals.insert(self.inputs.index(i), 0) 

83 

84 duals.append(np.array(fduals)) 

85 

86 # if the number of arguments is equal to the number of inputs 

87 else: 

88 # set the arguments to be the inputs 

89 args = x 

90 

91 # convert every element in args to a dual number with dual component 0 except for the target 

92 for i in range(len(args)): 

93 target_i = [Dual(arg, 0) for arg in args] 

94 target_i[i] = Dual(args[i]) 

95 z = f(*target_i) 

96 fduals.append(z.dual) 

97 

98 duals.append(np.array(fduals)) 

99 

100 # convert every element in args to a dual number with dual component 1 

101 args = [Dual(arg) for arg in args] 

102 

103 # unpack args and pass into f 

104 z = f(*args) 

105 reals.append(z.real) 

106 

107 return np.array([np.array(reals), duals], dtype = object) 

108 

109 # if there is one function 

110 else: 

111 # get the number of arguments 

112 n = len(inspect.getfullargspec(self.f)[0]) 

113 

114 # convert every element in args to a dual numebr with dual component 0 except for the target 

115 for i in range(len(x)): 

116 target_i = [Dual(arg, 0) for arg in x] 

117 target_i[i] = Dual(x[i]) 

118 z = self.f(*target_i) 

119 duals.append(z.dual) 

120 

121 # convert every element in args to a dual number with dual component 1 

122 args = [Dual(arg) for arg in x] 

123 

124 # unpack args and pass into f 

125 z = self.f(*args) 

126 reals = z.real 

127 

128 return np.array([reals, np.array(duals)], dtype = object)