def test_pow(): """Test of exponent special method (__pow__) of Rnode class.""" # Test for exponent with scalar Rnode object and float value x = Rnode(0.11) z = x**2 z.grad_value = 1.0 try: assert z.value == x.value**2 assert x.grad() == x.value**2 * np.log(x.value) # assert x.children == (x.value ** 2 * np.log(x.value), z) except AssertionError as e: print(e) # Test for exponent with two scalar Rnode object x = Rnode(0.11) y = Rnode(0.2) z = x**y z.grad_value = 1.0 try: assert z.value == x.value**y.value assert x.grad() == x.value**y.value * np.log(x.value) except AssertionError as e: print(e)
def test_clear(): """Test of clear special method (clear) of Rnode class.""" # Test for clear with scalar Rnode object and float value x = Rnode(0.11) z = x**2 + x z.grad_value = 1.0 x.grad() x.clear() try: # assert z.value == x.value **2 + x.value assert x.grad() is None except AssertionError as e: print(e)
def test_tanh(): """Test of tanh method.""" # Test for sin with Rnode objects x = Rnode(1.0) z = Elem.tanh(x) z.grad_value = 1.0 try: assert z.value == np.tanh(x.value) assert x.grad() == 1 / np.cosh(x.value)**2 except AssertionError as e: print(e) # Test for tan with two Dual objects val = Dual(3, [4, 1]) z = Elem.tanh(val) der = val.der / (np.cosh(val.val))**2 try: assert z.val == np.tanh(val.val) assert np.all(z.der == der) except AssertionError as e: print(e) raise AssertionError # Test for tanh with int, x = 3 fx = Elem.tanh(x) try: assert fx == np.tanh(x) except AssertionError as e: print(e) raise AssertionError
def test_cosh(): """Test of cosh method.""" # Test for sin with Rnode objects x = Rnode(1.0) z = Elem.cosh(x) z.grad_value = 1.0 try: assert z.value == np.cosh(x.value) assert x.grad() == np.sinh(x.value) except AssertionError as e: print(e) # Test for cosh with two Dual objects val = Dual(3, [4, 1]) z = Elem.cosh(val) try: assert z.val == np.cosh(val.val) assert z.der[0] == np.sinh(val.val) * val.der[0] assert z.der[1] == np.sinh(val.val) * val.der[1] except AssertionError as e: print(e) raise AssertionError # Test for cosh with int, x = 3 fx = Elem.cosh(x) try: assert fx == np.cosh(x) except AssertionError as e: print(e) raise AssertionError
def test_add(): """Test of addition special method (__add__) of Rnode class.""" # Test for addition with scalar Rnode object and float value x = Rnode(0.11) z = x**2 + x z.grad_value = 1.0 try: assert z.value == x.value**2 + x.value assert x.grad() == sum(weight * var.grad() for weight, var in x.children) except AssertionError as e: print(e)
def test_arccos(): """Test of arccos method.""" # Test for arccos with Rnode objects x = Rnode(0.11) z = Elem.arccos(x) z.grad_value = 1.0 temp = 1 - x.value**2 print(temp) if temp <= 0: raise ValueError('Domain of sqrt is {x >= 0}') try: assert z.value == np.arccos(x.value) assert x.grad() == -1 / np.sqrt(temp) except AssertionError as e: print(e) # Test for arccos with invalid Rnode objects with pytest.raises(ValueError, match=r".* sqrt .*"): x = Rnode(2.0) z = Elem.arccos(x) Elem.arcsin(x) z.grad_value = 1.0 # Test for arccos with two Dual objects # arccos() input (-1,1) x = Dual(0.2, [0.4, 0.1]) z = Elem.arccos(x) print(z) der = -1 / np.sqrt(1 - x.val**2) * np.asarray(x.der) try: assert z.val == np.arccos(x.val) assert np.all(z.der == der) except AssertionError as e: print(e) raise AssertionError # Test for arccos with int x = 0.1 fx = Elem.arccos(x) try: assert fx == np.arccos(x) except AssertionError as e: print(e) raise AssertionError
def test_relu6(): """Test of relu6 method.""" # Test for sin with Rnode objects x = Rnode(7.0) z = Elem.relu6(x) z.grad_value = 1.0 a = max(0, x.value) b = np.where(0.0 < a < 6.0, 1, 0) if a > 6.0: # clip output to a maximum of 6 a = 6.0 try: assert z.value == a assert x.grad() == b except AssertionError as e: print(e) # Test for relu6 with two Dual objects x = Dual(8, [4, 1]) z = Elem.relu6(x) a = max(0, x.val) b = np.where(0.0 < a < 6.0, 1, 0) print(b) if a > 6: # clip output to a maximum of 6 a = 6 result = Dual(a, b * x.der) try: assert z.val == result.val assert np.all(z.der == result.der) except AssertionError as e: print(e) raise AssertionError # Test for tanh with int, x = 3 fx = Elem.relu6(x) try: assert fx == min(max(0, x), 6) except AssertionError as e: print(e) raise AssertionError
def test_relu(): """Test of relu method.""" # Test for sin with Rnode objects x = Rnode(1.0) z = Elem.relu(x) z.grad_value = 1.0 a = max(0, x.value) b = np.where(a > 0, 1, 0) try: assert z.value == a assert x.grad() == b except AssertionError as e: print(e) # Test for relu with two Dual objects x = Dual(3, [4, 1]) z = Elem.relu(x) a = max(0, x.val) b = np.where(a > 0, 1, 0) result = Dual(a, b * x.der) try: assert z.val == result.val assert np.all(z.der == result.der) except AssertionError as e: print(e) raise AssertionError # Test for tanh with int, x = 3 fx = Elem.relu(x) try: assert fx == max(0, x) except AssertionError as e: print(e) raise AssertionError
def test_log2(): """Test of log2 method.""" # Test for sin with Rnode objects x = Rnode(1.0) z = Elem.log2(x) z.grad_value = 1.0 try: assert z.value == np.log2(x.value) assert x.grad() == 1 / (x.value * np.log(2)) except AssertionError as e: print(e) # Test for log2 with two Dual objects val1 = Dual(3, [4, 1]) val2 = Dual(2, [3, 1]) val = val1 * val2 z = Elem.log2(val) try: assert z.val == np.log2(val.val) assert z.der[0] == 1 / (val.val * np.log(2)) * val.der[0] assert z.der[1] == 1 / (val.val * np.log(2)) * val.der[1] except AssertionError as e: print(e) raise AssertionError # Test for log2 with invalid Dual objects with pytest.raises(ValueError, match=r".* logarithm .*"): Elem.log2(Dual(-1, [4, 1])) # Test for log2 with int, x = 3 fx = Elem.log2(x) try: assert fx == np.log2(x) except AssertionError as e: print(e) raise AssertionError
def test_sin(): """Test of sin method.""" # Test for sin with Rnode objects x = Rnode(1.0) z = Elem.sin(x) z.grad_value = 1.0 try: assert z.value == np.sin(x.value) assert x.grad() == np.cos(x.value) except AssertionError as e: print(e) raise AssertionError # Test for sin with two Dual objects val1 = Dual(3, [4, 1]) val2 = Dual(2, [3, 1]) val = val1 + val2 z = Elem.sin(val) try: assert z.val == np.sin(val.val) assert z.der[0] == np.cos(val.val) * val.der[0] assert z.der[1] == np.cos(val.val) * val.der[1] except AssertionError as e: print(e) raise AssertionError # Test for sin with int x = 3 fx = Elem.sin(x) try: assert fx == np.sin(x) except AssertionError as e: print(e) raise AssertionError
def test_sqrt(): """Test of sqrt method.""" # Test for sqrt with Rnode objects x = Rnode(1.0) z = Elem.sqrt(x) z.grad_value = 1.0 try: assert z.value == x.value**0.5 assert x.grad() == 0.5 * x.value**(-0.5) except AssertionError as e: print(e) # Test for sqrt with two Dual objects x = Dual(3, [4, 1]) z = Elem.sqrt(x) result = x**0.5 try: assert z.val == np.sqrt(x.val) assert z.der[0] == result.der[0] assert z.der[1] == result.der[1] except AssertionError as e: print(e) raise AssertionError # Test for sqrt with double x = 3.0 fx = Elem.sqrt(x) try: assert fx == np.sqrt(x) except AssertionError as e: print(e) raise AssertionError
def test_logistic(): """Test of logistic method.""" # Test for sin with Rnode objects x = Rnode(1.0) z = Elem.logistic(x) z.grad_value = 1.0 nominator = np.exp(x.value) denominator = (1 + np.exp(x.value))**2 try: assert z.value == 1 / (1 + np.exp(-x.value)) assert x.grad() == nominator / denominator except AssertionError as e: print(e) # Test for logistic with two Dual objects x = Dual(3, [4, 1]) z = Elem.logistic(x) result = (1 / (1 + np.exp(-x.val)), np.exp(x.val) / ((1 + np.exp(x.val))**2)) try: assert z == result except AssertionError as e: print(e) raise AssertionError # Test for logistic with int x = 3 fx = Elem.logistic(x) try: assert fx == 1 / (1 + np.exp(-x)) except AssertionError as e: print(e) raise AssertionError
def test_arctan(): """Test of arctan method.""" # Test for arctan with Rnode objects x = Rnode(0.11) z = Elem.arctan(x) z.grad_value = 1.0 try: assert z.value == np.arctan(x.value) assert x.grad() == 1 / (1 + x.value**2) except AssertionError as e: print(e) # Test for arctan with two Dual objects # arctan() input (-1,1) x = Dual(0.2, [0.4, 0.1]) z = Elem.arctan(x) der = 1 / (1 + x.val**2) * np.asarray(x.der) try: assert z.val == np.arctan(x.val) assert np.all(z.der == der) except AssertionError as e: print(e) raise AssertionError # Test for arctan with int x = 0.1 fx = Elem.arctan(x) try: assert fx == np.arctan(x) except AssertionError as e: print(e) raise AssertionError
def test_exp(): """Test of exp method.""" # Test for sin with Rnode objects x = Rnode(1.0) z = Elem.exp(x) z.grad_value = 1.0 try: assert z.value == np.exp(x.value) assert x.grad() == np.exp(x.value) except AssertionError as e: print(e) # Test for exp with two Dual objects x = Dual(3, [4, 1]) z = Elem.exp(x) der = np.exp(x.val) * np.asarray(x.der) try: assert z.val == np.exp(x.val) assert np.all(z.der == der) except AssertionError as e: print(e) raise AssertionError # Test for exp with int, x = 3 fx = Elem.exp(x) try: assert fx == np.exp(x) except AssertionError as e: print(e) raise AssertionError
class RAutoDiff: def __init__(self, fn): """Constructor for RAutoDiff class. Parameters ========== fn: The specific AD method for calculating the derivative. """ self.fn = fn self._roots = None self._value = None self._der = None def forwardpass(self, x): """Constructor the tree structure with input X for specific AD method fn. Update the value and derivative of the AD method. Parameters ========== x: array_like Returns: No returns. """ self._roots = None self._value = None self._der = None x = np.asarray(x) try: nf = len(self.fn) # if fn is a list of functions except TypeError: nf = 1 if nf == 1: # only one function self._value, self._der = self._forwardpass1f(x, self.fn) else: # vector functions #for now, all the input functions must contain exactly the same parameters #with the same order. function with only a subset of total Parameters # is not allowed nparams_all = [len(signature(fi).parameters) for fi in self.fn] if len(set(nparams_all)) > 1: raise TypeError('all input functions must contain the same parameters') self._value = [] self._der = [] for idxf in range(nf): tmp = self._forwardpass1f(x, self.fn[idxf]) self._value.append(tmp[0]) self._der.append(tmp[1]) self._value = np.asarray(self._value) self._der = np.asarray(self._der) if len(self._value.shape) == 2 and len(self._der.shape) == 2: self._value = np.transpose(self._value, (1, 0)) self._der = np.transpose(self._der, (1, 0)) elif len(self._value.shape) == 2 and len(self._der.shape) == 3: self._value = np.transpose(self._value, (1, 0)) self._der = np.transpose(self._der, (1, 0, 2)) try: # if _value or _der is a scalar array (size = 1), convert array to scalar if self._value.size ==1: self._value = np.asscalar(self._value) if self._der.size == 1: self._der = np.asscalar(self._der) except AttributeError: pass def _forwardpass1f(self, x, fi): # deal with only one function case """Constructor the tree structure with input X for one single AD method fi. This _forwardpass1f method will be called when dealing with vector function input fn = [f1, f2, f3, ...] Parameters ========== x: array_like fi: One AD method Returns: tmpval: array_like, the value of fi(x) tmpder : array_like, the derivative of fi(x) """ # shoule only be called from forwardpass self._roots = None nparams = len(signature(fi).parameters) # number of parameters of input function if nparams == 1: # function has only one parameter if x.size == 1: # scalar input self._roots = Rnode(x) f = fi(self._roots) f.grad_value = 1.0 tmpval = f.value tmpder = self._roots.grad() else: # vector input if len(x.shape) > 1: raise TypeError('input dimension size not supported') else: tmpval = np.zeros(len(x)) tmpder = np.zeros(len(x)) tmpidx = 0 for xi in x: self._roots = Rnode(xi) f = fi(self._roots) f.grad_value = 1.0 tmpval[tmpidx] = f.value tmpder[tmpidx] = self._roots.grad() tmpidx = tmpidx + 1 else: # multiple input parameters (vector input) if x.size == 1: raise TypeError('input has insufficient parameters') if len(x.shape) == 1: # evaluate at one vector point if len(x) != nparams: raise TypeError('input dimension size mismatch') self._roots = [Rnode(xi) for xi in x] f = fi(*self._roots) f.grad_value = 1.0 tmpval = f.value tmpder = [root.grad() for root in self._roots] else: # evaluate at multiple vector points if x.shape[1] != nparams: raise TypeError('input dimension size mismatch') nm = x.shape[0] # number of vector points to be evaluated tmpval = np.zeros(nm) tmpder = np.zeros((nm, nparams)) self._roots = [] for im in range(nm): self._roots.append([Rnode(xi) for xi in x[im, :]]) f = fi(*self._roots[im]) f.grad_value = 1.0 tmpval[im] = f.value tmpder[im, :] = [root.grad() for root in self._roots[im]] return tmpval, tmpder def values(self): # return the value of the function """Get value of the input method fn for given X Returns: y : array_like """ return self._value def reverse(self): # return the derivative with respect to varname variable """Get the derivatives (scalar, vector, or matrix depending on the the dimension of input method fn and X) Returns: y : array_like """ return self._der