def test_array_input(): arr = [Variable('x', 0.5), Variable('x', 0.5)] t1 = sin(arr) t2 = arcsin(arr) t3 = cos(arr) t4 = arccos(arr) t5 = tan(arr) t6 = arctan(arr) t7 = exp(arr) t8 = log(arr) t9 = sinh(arr) t10 = cosh(arr) t11 = tanh(arr) t12 = sqrt(arr) t13 = sigmoid(arr) assert t1[0].val == pytest.approx(np.sin(0.5)) assert t2[1].val == pytest.approx(np.arcsin(0.5)) assert t3[0].val == pytest.approx(np.cos(0.5)) assert t4[1].val == pytest.approx(np.arccos(0.5)) assert t5[0].val == pytest.approx(np.tan(0.5)) assert t6[1].val == pytest.approx(np.arctan(0.5)) assert t8[0].val == pytest.approx(np.log(0.5)) assert t9[1].val == pytest.approx(np.sinh(0.5)) assert t10[0].val == pytest.approx(np.cosh(0.5)) assert t11[1].val == pytest.approx(np.tanh(0.5)) assert t12[0].val == pytest.approx(np.sqrt(0.5)) assert t13[1].val == pytest.approx(1 / (1 + np.exp(-0.5)))
def test_string_input_var(): with pytest.raises(TypeError): x = Variable('x', 'test') with pytest.raises(TypeError): x = Variable('x', 3) x.val = 2 x.val = 'test'
def test_sqrt(): value = 9 x = Variable('x', value) a = sqrt(value) assert a == pytest.approx(np.sqrt(value)) g = sqrt(x) assert g.val == pytest.approx(np.sqrt(value))
def test_sigmoid(): value = 0.5 x = Variable('x', value) g = sigmoid(x) a = sigmoid(value) assert a == pytest.approx(1 / (1 + np.exp(-value))) assert g.val == pytest.approx(1 / (1 + np.exp(-value)))
def test_log(): value = 4 x = Variable('x', value) f = log(x) a = log(value) assert a == pytest.approx(np.log(value)) assert f._val == np.log(value) assert f._der['v1'] == pytest.approx(0.25)
def test_exp(): value = 67 x = Variable('x', value) f = exp(x) a = exp(value) assert a == pytest.approx(np.exp(value)) assert f._val == pytest.approx(np.exp(value), rel=1e-5) assert f._der['v1'] == f._val
def test_other_domains(): x = Variable('x', -2) y = -2 with pytest.raises(ValueError): f = log(x) with pytest.raises(ValueError): f = log(y) with pytest.raises(ValueError): f = sqrt(x) with pytest.raises(ValueError): f = sqrt(y)
def test_arc_domains(): x = Variable('x', 2) y = 2 with pytest.raises(ValueError): f = arcsin(x) with pytest.raises(ValueError): f = arccos(x) with pytest.raises(ValueError): f = arcsin(y) with pytest.raises(ValueError): f = arccos(y)
def test_composition_val(): value = np.pi / 6 x = Variable('x', value) c = cos(x) s = sin(x) t = tan(x) e = exp(x) f = c * t + e g = c + s assert isinstance(f, Trace) assert f._val == np.cos(value) * np.tan(value) + np.exp(value)
def test_sin(): value = 0.5 x = Variable('x', value) a = sin(value) b = arcsin(value) c = sinh(value) assert a == pytest.approx(np.sin(value)) assert b == pytest.approx(np.arcsin(value)) assert c == pytest.approx(np.sinh(value)) g = sin(x) h = arcsin(x) i = sinh(x) assert g.val == pytest.approx(np.sin(value)) assert h.val == pytest.approx(np.arcsin(value)) assert i.val == pytest.approx(np.sinh(value))
def test_tan(): value = 0.5 x = Variable('x', value) a = tan(value) b = arctan(value) c = tanh(value) assert a == pytest.approx(np.tan(value)) assert b == pytest.approx(np.arctan(value)) assert c == pytest.approx(np.tanh(value)) g = tan(x) h = arctan(x) i = tanh(x) assert g.val == pytest.approx(np.tan(value)) assert h.val == pytest.approx(np.arctan(value)) assert i.val == pytest.approx(np.tanh(value))
def test_cos(): value = 0.5 x = Variable('x', value) a = cos(value) b = arccos(value) c = cosh(value) assert a == pytest.approx(np.cos(value)) assert b == pytest.approx(np.arccos(value)) assert c == pytest.approx(np.cosh(value)) g = cos(x) h = arccos(x) i = cosh(x) assert g.val == pytest.approx(np.cos(value)) assert h.val == pytest.approx(np.arccos(value)) assert i.val == pytest.approx(np.cosh(value))
def trace(f, seed, mode=None, return_second_deriv=False, verbose=False): ''' f : a function seed: a vector/list of scalars. If f is single-dimensional, seed can be a scalar Optional parameter mode When mode = None, this function infers the more efficient mode from the number of input and output variables Optional parameter return_second_deriv (default is False) When return_second_deriv = True, this function returns f' AND f'' f can be f: R --> R using explicit single-variable input f: Rm --> R using explicit multi-variable input f: R --> Rn using explicit single-variable input and explicit vector output f: Rm --> R using explicit vector input f: Rm --> Rn using explicit vector input and explicit vector output f: Rm --> Rn using explicit multi-variable input and explicit vector output f: Rm --> Rn using IMPLICIT vector input and IMPLICIT vector output ''' ######################## make your Variable objects ######################### # for now, always reset the CompGraph when tracing a new function CompGraph.reset() # infer the dimensionality of the input try: # if multidimensional input M = len(seed) # get the dimension of the input seed = np.array(seed) except TypeError: # if single-dimensional input M = 1 seed = np.array([seed]) if verbose: print(f'Inferred {M}-dimensional input') # create new variables names = [f'v{i+1}' for i in range(M)] new_variables = np.array([Variable(names[i], seed[i]) for i in range(M)]) ############################################################################# ################ Trace the function ############## if verbose: print('Scanning the computational graph...') # Apply f to the new variables # Infer the way f was meant to be applied if M > 1: # multi-variable input try: # as a vector output = f(new_variables) if verbose: print('...inferred the input is a vector...') except TypeError: # as variables output = f(*new_variables) if verbose: print('...inferred the inputs are variables...') else: # single-variable input output = f(new_variables[0]) if verbose: print('...inferred the input is a variable...') if verbose: print('...finished') ############################################ ################ Get Outputs ################# try: N = len(output) except AttributeError: N = 1 output = [output] except TypeError: N = 1 output = [output] if verbose: print(f'Inferred {N}-dimensional output') print(output) ############################################## ##################### Second Derivative ######################### if return_second_deriv: if mode is not None and mode.lower() != 'reverse': raise ValueError( 'Second derivative is automatically calculated in reverse mode' ) if N > 1: raise ValueError( 'Can only compute second derivative for scalar output f') print('Computing reverse mode first AND second derivative...') return CompGraph.hessian(output, verbose) ###################################################### ####### get user-defined mode or infer the more efficient mode ########## if mode is None: if M > N: mode = 'reverse' else: mode = 'forward' else: mode = mode.lower() ###################################################################### ############## First Derivative #################### #if verbose: print(f'Computing {mode} mode derivative...') if mode == 'forward': return CompGraph.forward_mode(output, verbose) elif mode == 'reverse': return CompGraph.reverse_mode(output, verbose) else: raise ValueError('Didnt recognize mode, should be forward or reverse')
def test_exp_base2(): x = Variable('x', 5) base = 2 f = exp(x, base=base) assert f._val == pytest.approx(32) assert f._der['v1'] == (base**x._val) * np.log(base)
def test_log_base2(): x = Variable('x', 32) base = 2 f = log(x, base=base) assert f._val == pytest.approx(5) assert f._der['v1'] == 1 / (x._val * np.log(base))