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
« 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)
6import inspect
7import numpy as np
9from autodiff.ad import AD
10from autodiff.dual import Dual
12class ForwardMode(AD):
13 """Forward mode implementation based on dual number data structure."""
15 def get_results(self, x):
16 """
17 Compute the value(s) and the derivative(s) of the function(s) based on input 'x'.
19 Parameters
20 ----------
21 x : Scalar, Vector.
22 The point at which the value(s) and derivative(s) of the function(s) are evaluated.
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'.
29 Raises
30 ------
31 TypeError
32 This method raises a `TypeError` if the type of input 'x' is not supported.
34 ValueError
35 This method also raises a `ValueError` if the dimension of input 'x' is not matched with the function(s).
37 """
38 reals = []
39 duals = []
41 # check that x is of supported type
42 if not isinstance(x, self._supported_vectors):
43 raise TypeError(f"Unsupported type '{type(x)}'")
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.")
49 # convert x to a list
50 x = list(x)
52 # if there are multiple functions
53 if self.jacobian:
54 for f in self.f:
55 # initialize arguments list
56 args = []
57 fduals = []
59 # get the number of arguments
60 n = len(inspect.getfullargspec(f)[0])
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]
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)])
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)
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)
84 duals.append(np.array(fduals))
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
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)
98 duals.append(np.array(fduals))
100 # convert every element in args to a dual number with dual component 1
101 args = [Dual(arg) for arg in args]
103 # unpack args and pass into f
104 z = f(*args)
105 reals.append(z.real)
107 return np.array([np.array(reals), duals], dtype = object)
109 # if there is one function
110 else:
111 # get the number of arguments
112 n = len(inspect.getfullargspec(self.f)[0])
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)
121 # convert every element in args to a dual number with dual component 1
122 args = [Dual(arg) for arg in x]
124 # unpack args and pass into f
125 z = self.f(*args)
126 reals = z.real
128 return np.array([reals, np.array(duals)], dtype = object)