def test_exp_log(): """Test exponents and logarithms""" x = np.linspace(-5, 5, 21) exp_x = np.exp(x) y = exp_x # Evaluate exp(x), log(y) _exp, _dexp = fl.exp(x)() _log, _dlog = fl.log(y)() # Known answers assert np.allclose(_exp, exp_x) assert np.allclose(_dexp, exp_x) assert np.allclose(_log, x) assert np.allclose(_dlog, 1.0 / y) # Log base 2 and 10; exp base 2 _log2, _dlog2 = fl.log2(y)() _log10, _dlog10 = fl.log10(y)() _exp2, _dexp2 = fl.exp2(x)() # Known answers assert np.allclose(_log2, x / np.log(2.0)) assert np.allclose(_dlog2, 1.0 / y / np.log(2.0)) assert np.allclose(_log10, x / np.log(10.0)) assert np.allclose(_dlog10, 1.0 / y / np.log(10.0)) assert np.allclose(_exp2, 2.0**x) assert np.allclose(_dexp2, np.log(2.0) * (2.0**x)) # exponential minus 1, log plus 1 _expm1, _dexpm1 = fl.expm1(x)() _log1p, _dlog1p = fl.log1p(y)() # Known answers assert np.allclose(_expm1, exp_x - 1.0) assert np.allclose(_dexpm1, exp_x) assert np.allclose(_log1p, np.log(1.0 + y)) assert np.allclose(_dlog1p, 1.0 / (1.0 + y)) # exponential minus 1, log plus 1 _logaddexp, _dlogaddexp = fl.logaddexp(x, x)() _logaddexp2, _dlogaddexp2 = fl.logaddexp2(x, x)() assert (str(fl.logaddexp( fl.Var('x'), fl.Var('y'))) == "logaddexp(Var(x, None),Var(y, None))") # Known answers assert np.allclose(_logaddexp, np.logaddexp(x, x)) assert np.allclose(_dlogaddexp, np.vstack([0 * y + 1 / 2, 0 * y + 1 / 2]).T) assert np.allclose(_logaddexp2, np.logaddexp2(x, x)) assert np.allclose(_dlogaddexp2, np.vstack([0 * y + 1 / 2, 0 * y + 1 / 2]).T) # forward mode f = fl.logaddexp(fl.Var('x'), fl.Var('y')) val, diff = f(0, 0) assert (np.isclose(val, np.array([[0.69314718]]))) assert (diff.all() == np.array([[0.5, 0.5]]).all()) report_success()
def test_compositions(): """ TEST: composition of elementary functions: (i) compositions of multiple elementary functions (ii) compositions of elementary functions & other ops (Fluxions) """ theta_vec = np.expand_dims(np.linspace(-5, 5, 21) * np.pi, axis=1) # composition of 2 elementary functions: # (a) immediate evaluation val, diff = fl.log(fl.exp(theta_vec))() assert (np.all(val == theta_vec)) assert (np.all(np.isclose(diff, 1.0))) # (b) delayed evaluation logexp = fl.log(fl.exp(fl.Var('theta'))) assert (np.all(logexp.val({'theta': theta_vec}) == theta_vec)) assert (np.all( np.isclose(logexp.diff({'theta': theta_vec}), np.ones_like(theta_vec)))) # composition of elementary functions and basic ops (other Fluxions) # (a) immediate evaluation ans_1 = fl.cos(theta_vec).val()**2 - fl.sin(theta_vec).val()**2 ans_2 = fl.cos(2 * theta_vec).val() assert (np.all(np.isclose(ans_1, ans_2))) # (b) delayed evaluation theta = fl.Var('theta', theta_vec) f = fl.cos(theta)**2 - fl.sin(theta)**2 assert (np.all( np.isclose(f.val({'theta': theta_vec}), fl.cos(2 * theta_vec).val()))) report_success()
def test_jacobian_dims(): # test different possible combinations of function input and output dimensions global x, y, z J = jacobian([x * y, x**2, x + y], ['x', 'y'], {'x': 2, 'y': 3}) assert (np.all(J == np.array([[3, 2], [4, 0], [1, 1]]).T)) # Jacobian of a (1x1) function is the same as its derivative #F = fl.sin(fl.log(x**x)) F = fl.sin(fl.log(x)) J = jacobian(F, ['x'], {'x': 2}) assert (np.all(J == F.diff({'x': 2}))) # Jacobian of a scalar (3x1) function (= transpose of its gradient) J = jacobian([2 * x + x * y**3 + fl.log(z)], ['z', 'y', 'x'], { 'x': 2, 'y': 3, 'z': 4 }) assert (np.all(J == np.array([29., 54., 0.25]))) # partials with respect to only one variable -> Jacobian is still mxn #J = jacobian([2*x + z*y**3 + fl.log(z)], ['z'], {'x':2, 'y':3, 'z':4}) #assert(np.all(J == np.array(27.25))) # NOTE: SHOULD THIS BE np.array([27.25])? --> Good call - code throws an error now! J = jacobian([x + y, y], ['x', 'y', 'z'], {'x': 2, 'y': 3, 'z': 1}) assert (np.all(J == np.array([[1, 1, 0], [0, 1, 0]]).T)) r = fl.Var('r') theta = fl.Var('theta') F = [r * fl.cos(theta), r * fl.sin(theta)]
def test_basics_singlevar(): theta_vec = np.expand_dims(np.linspace(-5, 5, 21) * np.pi, axis=1) #### TEST: passing in vector of values: "immediate" evaluation _cos, _dcos = fl.cos(theta_vec)() _sin, _dsin = fl.sin(theta_vec)() _tan, _dtan = fl.tan(theta_vec)() assert (all(_dcos == -_sin)) assert (all(np.isclose(_tan, _sin / _cos))) #### TEST: passing a fluxion Var: "delayed" evaluation theta = fl.Var('theta') _cos = fl.cos(theta) _sin = fl.sin(theta) assert (np.all( np.isclose(_sin.diff({'theta': theta_vec}), np.cos(theta_vec)))) assert (np.all( _cos.diff({'theta': theta_vec}) == -1 * _sin.val({'theta': theta_vec}))) #### TEST: basic functionality of other elementary functions # tan' = sec^2 _dtan = (fl.tan(theta)).diff({'theta': theta_vec}) _sec2 = ((1 / fl.cos(theta))**2).val({'theta': theta_vec}) assert (np.all(np.isclose(_dtan, _sec2))) # test Fluxions returns NaN as numpy does x = np.linspace(-5, 5, 21) _varx = fl.Var('x') _log = fl.log(_varx) # This test is tricky. two subtleties: # (1) do everything under this with statement to catch runtime warnings about bad log inputs # (2) can't just compare numbers with ==; also need to compare whether they're both nans separately # this is because nan == nan returns FALSE in numpy! with np.warnings.catch_warnings(): np.warnings.filterwarnings('ignore') _log_fl = _log.val({'x': x}) _log_np = np.expand_dims(np.log(x), axis=1) assert (np.all((_log_fl == _log_np) | (np.isnan(_log_fl) == np.isnan(_log_np)))) _log_der_1 = _log.diff({'x': x}) _log_der_2 = 1 / _varx.val({'x': x}) assert (np.all((_log_der_1 == _log_der_2))) _hypot, _hypot_der = fl.hypot( fl.sin(theta_vec).val(), fl.cos(theta_vec).val())() assert (np.all(_hypot == np.ones_like(theta_vec))) # test arccos vs. sec? report_success()
def test_basics_multivar(): """ TEST: elementary functions of multiple variables """ theta_vec = np.expand_dims(np.linspace(-5, 5, 21) * np.pi, axis=1) sin_z = fl.sin(fl.Var('x') * fl.Var('y')) assert (np.all( np.isclose(sin_z.val({ 'x': theta_vec, 'y': theta_vec }), np.sin(theta_vec**2)))) #WHAT TO DO WHEN TOO FEW VARS PASSED? # sin_z.val({'x':theta_vec}) # HOW ARE PARTIALS EVALUATED? # sin_z.diff({'x': theta_vec}) # sin_z.diff({'x': theta_vec, 'y': theta_vec*2}) report_success()
def test_elementary_functions(): # Create a variable theta with angles from 0 to 360 degrees, with values in radians theta_val = np.expand_dims(np.linspace(0, 2 * np.pi, 361), axis=1) theta = fl.Var('theta') # Scalar version f_theta = fl.sin(theta) assert f_theta.val({'theta': 2}) == np.sin(2) assert f_theta.diff({'theta': 2}) == np.cos(2) assert (str(f_theta) == "sin(Var(theta, None))") # Vector version f_theta = fl.sin(theta) assert np.all(f_theta.val({'theta': theta_val}) == np.sin(theta_val)) assert np.all(f_theta.diff({'theta': theta_val}) == np.cos(theta_val)) report_success()
import fluxions as fl def newtons_method_scalar(f: fl.Fluxion, x: float, tol: float = 1e-8) -> float: """Solve the equation f(x) = 0 for a function from R->R using Newton's method""" max_iters: int = 100 for i in range(max_iters): # Evaluate f(x) and f'(x) y, dy_dx = f(x) # Is y within the tolerance? if abs(y) < tol: break # Compute the newton step dx = -y / dy_dx # update x x += float(dx) # Return x and the number of iterations required return x, i # Use this newton's method solver to find the root to equation # e^x = 10x x = fl.Var('x') f = fl.exp(x) - 10 * x root, iters = newtons_method_scalar(f, 0.0) f_root = float(f.val(root)) print(f'Solution of exp(x) == 10x by Newton' 's Method:') print(f'Solution converged after {iters} iterations.') print(f'x={root:0.8f}, f(x) = {f_root:0.8f}')
def test_basic_usage(): """Test basic usage of Fluxions objects""" # Create a variable, x x = fl.Var('x', 1.0) #f0 = x - 1 f0 = x - 1 assert (f0.val({'x': 1}) == 0) assert (f0.diff({'x': 1}) == 1) var_tbl = {'x': 1} seed_tbl = {'x': 1} val, diff = f0(var_tbl, seed_tbl) assert val == 0 assert diff == 1 assert repr(f0) == "Subtraction(Var(x, 1.0), Const(1.0))" # f1(x) = 5x f1 = 5 * x assert (f1.shape() == (1, 1)) # Evaluate f1(x) at the bound value of x assert (f1() == (5.0, 5.0)) assert (f1(None) == (5.0, 5.0)) assert (f1(1, 1) == (5.0, 5.0)) assert (f1(np.array(1), np.array(1)) == (5.0, 5.0)) # Evaluate f1(x) using function calling syntax assert (f1(2) == (10.0, 5.0)) # Evaluate f1(x) using dictionary binding syntax assert (f1.val({'x': 2}) == 10) assert (f1.diff({'x': 2}) == 5) assert (f1({'x': 2}) == (10.0, np.array([5.]))) assert repr(f1) == "Multiplication(Var(x, 1.0), Const(5.0))" # f2(x) = 1 + (x * x) f2 = 1 + x * x assert (f2(4.0) == (17.0, 8.0)) assert (f2.val({'x': 2}) == 5) assert (f2.diff({'x': 3}) == 6) # f3(x) = (1 + x)/(x * x) f3 = (1 + x) / (x * x) assert (f3.val({'x': 2}) == 0.75) assert (f3.diff({'x': 2}) == -0.5) assert repr( f3 ) == "Division(Addition(Var(x, 1.0), Const(1.0)), Multiplication(Var(x, 1.0), Var(x, 1.0)))" # f4(x) = (1 + 5x)/(x * x) f4 = (1 + 5 * x) / (x * x) assert (f4.val({'x': 2}) == 2.75) assert (f4.diff({'x': 2}) == -1.5) # Take a power f5 = fl.Power(x, 2) assert (f5.val(8) == 64) assert (f5.diff(8) == 16) assert (f5() == (1.0, 2.0)) assert (f5(1) == (1.0, 2.0)) assert (f5({'x': 1}) == (1.0, 2.0)) assert repr(f5) == "Power(Var(x, 1.0), 2)" #check assignment a = fl.Fluxion() b = fl.Unop(a) c = fl.Var('x') assert (c.diff(0) == 1) assert (c.diff({'x': 1}) == 1) assert (c.diff({'x': 1}, {'x': 2}) == 2) assert (np.array_equal(c.diff({ 'x': 1, 'y': 1 }, { 'x': 2, 'y': 1 }), np.array([[2., 0.]]))) assert (c(1) == (1, np.array([1]))) #check division f6 = 1 / x assert (f6.val({'x': 1, 'y': 1}) == 1) assert (np.array_equal(f6.diff({'x': 1, 'y': 1}), np.array([[-1., 0.]]))) #check subtraction and division f7 = (1 - x + 1 - 1) / ((x * x) / 1) assert (f7.val({'x': 2}) == -0.25) assert (f7.diff({'x': 2}) == 0) # check negation f8 = -x assert (f8.val({'x': 2}) == -2) assert (f8.diff({'x': 2}) == -1) y = fl.Var('y') f9 = -(x * y) assert (f9.val({'x': -2, 'y': 3}) == 6) val, diff = f9(1, 1, 1, 1) assert (val == np.array([[-1.]])) assert (val == np.array([[-1., -1.]])).all()
def test_basics_vectors(): """Test using Fluxions objects with vector inputs""" # Create some vectors n = 10 xs = np.expand_dims(np.linspace(0, 1, num=n), axis=1) ys = np.linspace(1, 2, num=n) ys_ex = np.expand_dims(np.linspace(1, 2, num=n), axis=1) # Create variables x and y bound to vector values x = fl.Var('x', xs) y = fl.Var('y', ys) # f1(x) = 5x f1 = 5 * x assert (f1.val(xs) == 5 * xs).all() assert (f1.diff({'x': xs}) == 5.0 * np.ones(np.shape(xs))).all() val, diff = f1(ys) assert (val == 5.0 * ys_ex).all() assert (diff == 5.0 * np.ones(np.shape(xs))).all() # f2(x) = 1 + (x * x) f2 = 1 + x * x assert (f2.val({'x': xs}) == 1 + np.power(xs, 2)).all() assert (f2.diff({'x': xs}) == 2.0 * xs).all() # f3(y) = (1 + y)/(y * y) f3 = (1 + y) / (y * y) assert (f3.val({'y': ys}) == np.divide(1 + ys_ex, np.power(ys_ex, 2))).all() assert np.isclose( f3.diff({'y': ys_ex}), np.divide(-2 - ys_ex, np.multiply(np.power(ys_ex, 2), ys_ex))).all() # f(x) = (1 + 5x)/(x * x) f4 = (1 + 5 * x) / (x * x) assert (f4.val({'x': ys}) == np.divide(1 + 5 * ys_ex, np.power(ys_ex, 2))).all() assert np.isclose( f4.diff({'x': ys}), np.divide(-2 - 5 * ys_ex, np.multiply(np.power(ys_ex, 2), ys_ex))).all() # f5(x,y) = 5x+y f5 = 5 * x + y var_tbl_scalar = {'x': 2, 'y': 3} var_tbl_vector = {'x': xs, 'y': xs} assert (f5.val(var_tbl_scalar) == 13) assert (f5.diff(var_tbl_scalar) == np.array([5, 1])).all() assert (f5.val(var_tbl_vector) == 5 * xs + xs).all() assert (f5.diff(var_tbl_vector) == np.asarray([np.array([5, 1])] * n)).all() # f(x,y) = 5xy f6 = 5 * x * y assert (f6.val(var_tbl_scalar) == 30) assert (f6.diff(var_tbl_scalar) == np.array([15, 10])).all() assert (f6.val(var_tbl_vector) == np.multiply(5 * xs, xs)).all() assert (f6.diff(var_tbl_vector) == np.transpose([5 * xs, 5 * xs])).all() # f(x,y,z) = 3x+2y+z z = fl.Var('z') f7 = 3 * x + 2 * y + z var_tbl_scalar = {'x': 1, 'y': 1, 'z': 1} assert (f7.val(var_tbl_scalar) == 6) assert (f7.diff(var_tbl_scalar) == np.array([3, 2, 1])).all() var_tbl_vector = {'x': xs, 'y': xs, 'z': xs} assert (f7.val(var_tbl_vector) == 3 * xs + 2 * xs + xs).all() assert (f7.diff(var_tbl_vector) == np.asarray([np.array([3, 2, 1])] * 10)).all() var_tbl_vector = {'z': xs} f7.val(var_tbl_vector) assert (f7.val(var_tbl_vector) == 3 * xs + 2 * xs + xs + 2).all() # f(x,y,z) = (3x+2y+z)/xyz f8 = (x * 3 + 2 * y + z) / (x * y * z) assert (f8.val(var_tbl_scalar) == 6) assert (f8.diff(var_tbl_scalar) == np.array([-3., -4., -5.])).all() # Rebind 'x', 'y', ans 'z' to the values in ys (slightly tricky!) var_tbl_vector = {'x': ys, 'y': ys, 'z': ys} assert (f8.val(var_tbl_vector) == (3 * ys_ex + 2 * ys_ex + ys_ex) / (ys_ex * ys_ex * ys_ex)).all() assert np.isclose( f8.diff(var_tbl_vector), np.transpose([ -3 * ys / np.power(ys, 4), -4 * ys / np.power(ys, 4), -5 * ys / np.power(ys, 4) ])).all() #f(x,y) = xy f9 = y * x assert (f9.val({'x': 0, 'y': 0, 'z': 1}) == 0).all() assert (f9.diff({ 'x': 0, 'y': 0, 'z': 1 }) == np.asarray([np.array([0, 0, 0])])).all()
import os # Handle import of module fluxions differently if module # module is being loaded as __main__ or a module in a package. if __name__ == '__main__': cwd = os.getcwd() os.chdir('../..') import fluxions as fl from fluxions import jacobian os.chdir(cwd) else: import fluxions as fl from fluxions import jacobian # ************************************************************************************************* x = fl.Var('x') y = fl.Var('y') z = fl.Var('z') def test_vectorization(): global x, y, z # Jacobian of a function from R^2 -> R^3, T=1 is squeezed to remove T dimension J = jacobian([x * y, x**2, x + y], ['x', 'y'], {'x': 1, 'y': 2}) assert (J.shape == (2, 3)) assert (J == np.array([[2., 2., 1.], [1., 0., 1.]])).all() # Jacobian of a function from R^2 -> R^3, T=4 v_mapping = {'x': list(np.linspace(1, 4, 4)), 'y': list(2 * np.ones(4))} J = jacobian([x * y, x**2, x + y], ['x', 'y'], v_mapping)