# Path: autodiff/decorators.py
# def function(fn=None, **kwargs):
#     """
#     Wraps a function with an AutoDiff Function instance, converting it to a
#     symbolic representation.
# 
#     The function is compiled the first time it is called.
# 
#     Use:
#         @function
#         def python_function(...):
#             return do_something()
# 
#         python_function(...) # calls compiled Function
# 
#     Pass keywords to Function:
# 
#         @function(force_floatX=True):
#             def python_function(x=1, y=2):
#                 return do_something()
#     """
#     if callable(fn):
#         return Function(fn, **kwargs)
#     else:
#         def function_wrapper(pyfn):
#             return Function(pyfn, **kwargs)
#         return function_wrapper
# 
# def gradient(fn=None, **kwargs):
#     """
#     Wraps a function with an AutoDiff Gradient instance, converting it to a
#     symbolic representation that returns the derivative with respect to either
#     all inputs or a subset (if specified with the 'wrt' keyword).
# 
#     The function is compiled the first time it is called.
#     Use:
# 
#         @gradient
#         def python_function(...):
#             return do_something()
# 
#         python_function(...) # returns the gradient of python_function
# 
#     Pass keywords to Gradient:
# 
#         @gradient(wrt = ['x', 'y'])
#         def python_function(x=1, y=2):
#             return do_something()
# 
#     """
#     if callable(fn):
#         return Gradient(fn, **kwargs)
#     else:
#         def gradient_wrapper(pyfn):
#             return Gradient(pyfn, **kwargs)
#         return gradient_wrapper
# 
# def hessian_vector(fn=None, **kwargs):
#     """
#     Wraps a function with an AutoDiff HessianVector instance, converting it to
#     a symbolic representation that returns the result with respect to either
#     all inputs or a subset (if specified with the 'wrt' keyword). A tuple of
#     the required vectors must be passed to the resulting function with the
#     keyword '_vectors'.
# 
#     The function is compiled the first time it is called.
#     Use:
# 
#         @gradient
#         def python_function(...):
#             return do_something()
# 
#         python_function(...) # returns the gradient of python_function
# 
#     Pass keywords to Gradient:
# 
#         @gradient(wrt = ['x', 'y'])
#         def python_function(x=1, y=2):
#             return do_something()
# 
#     """
#     if callable(fn):
#         return HessianVector(fn, **kwargs)
#     else:
#         def hv_wrapper(pyfn):
#             return HessianVector(pyfn, **kwargs)
#         return hv_wrapper
# 
# Path: autodiff/functions.py
# def tag(obj, tag):
#     """
#     Tags an object with a certain keyword. By default, all symbolic objects are
#     associated with the id of the Python object they shadow in a Context's
#     svars dict.  By calling this function on an object and providing a
#     (hashable) tag, users can more easily access the symbolic representation of
#     any objects that might only be created during function execution.
# 
#     Example:
# 
#         @function
#         def fn(x):
#             y = tag(x + 2, 'y')
#             z = y * 3
#             return z
# 
#         fn.s_vars['y'] # returns the symbolic version of y
# 
#     """
#     return obj


import unittest
import numpy as np
from autodiff.decorators import function, gradient, hessian_vector
from autodiff.functions import tag
        a = np.array(3.0)
        b = np.array(5.0)

        @gradient(wrt=a)
        def f(x, y):
            return x * y
        self.assertTrue(np.allclose(f(a, 5.0), 5.0))

        @gradient(wrt=b)
        def f(x, y):
            return x * y
        self.assertRaises(ValueError, f, a, 5.0)
        self.assertTrue(np.allclose(f(a, b), 3.0))
        self.assertTrue(np.allclose(f(3.0, b), 3.0))

        @gradient(wrt=(a, b))
        def f(x, y):
            return x * y
        self.assertRaises(ValueError, f, a, 5.0)
        self.assertTrue(np.allclose(f(a, b), [5.0, 3.0]))


class TestHV(unittest.TestCase):
    def test_hv_missing_vectors(self):
        @hessian_vector
        def fn(x):
            return x
        self.assertRaises(ValueError, fn, np.array([[1, 1]]))

    def test_hv_no_scalar(self):

# Path: autodiff/decorators.py
# def function(fn=None, **kwargs):
#     """
#     Wraps a function with an AutoDiff Function instance, converting it to a
#     symbolic representation.
# 
#     The function is compiled the first time it is called.
# 
#     Use:
#         @function
#         def python_function(...):
#             return do_something()
# 
#         python_function(...) # calls compiled Function
# 
#     Pass keywords to Function:
# 
#         @function(force_floatX=True):
#             def python_function(x=1, y=2):
#                 return do_something()
#     """
#     if callable(fn):
#         return Function(fn, **kwargs)
#     else:
#         def function_wrapper(pyfn):
#             return Function(pyfn, **kwargs)
#         return function_wrapper
# 
# def gradient(fn=None, **kwargs):
#     """
#     Wraps a function with an AutoDiff Gradient instance, converting it to a
#     symbolic representation that returns the derivative with respect to either
#     all inputs or a subset (if specified with the 'wrt' keyword).
# 
#     The function is compiled the first time it is called.
#     Use:
# 
#         @gradient
#         def python_function(...):
#             return do_something()
# 
#         python_function(...) # returns the gradient of python_function
# 
#     Pass keywords to Gradient:
# 
#         @gradient(wrt = ['x', 'y'])
#         def python_function(x=1, y=2):
#             return do_something()
# 
#     """
#     if callable(fn):
#         return Gradient(fn, **kwargs)
#     else:
#         def gradient_wrapper(pyfn):
#             return Gradient(pyfn, **kwargs)
#         return gradient_wrapper
# 
# def hessian_vector(fn=None, **kwargs):
#     """
#     Wraps a function with an AutoDiff HessianVector instance, converting it to
#     a symbolic representation that returns the result with respect to either
#     all inputs or a subset (if specified with the 'wrt' keyword). A tuple of
#     the required vectors must be passed to the resulting function with the
#     keyword '_vectors'.
# 
#     The function is compiled the first time it is called.
#     Use:
# 
#         @gradient
#         def python_function(...):
#             return do_something()
# 
#         python_function(...) # returns the gradient of python_function
# 
#     Pass keywords to Gradient:
# 
#         @gradient(wrt = ['x', 'y'])
#         def python_function(x=1, y=2):
#             return do_something()
# 
#     """
#     if callable(fn):
#         return HessianVector(fn, **kwargs)
#     else:
#         def hv_wrapper(pyfn):
#             return HessianVector(pyfn, **kwargs)
#         return hv_wrapper
# 
# Path: autodiff/functions.py
# def tag(obj, tag):
#     """
#     Tags an object with a certain keyword. By default, all symbolic objects are
#     associated with the id of the Python object they shadow in a Context's
#     svars dict.  By calling this function on an object and providing a
#     (hashable) tag, users can more easily access the symbolic representation of
#     any objects that might only be created during function execution.
# 
#     Example:
# 
#         @function
#         def fn(x):
#             y = tag(x + 2, 'y')
#             z = y * 3
#             return z
# 
#         fn.s_vars['y'] # returns the symbolic version of y
# 
#     """
#     return obj


# Path: autodiff/tests/test_decorators.py
import unittest
import numpy as np
from autodiff.decorators import function, gradient, hessian_vector
from autodiff.functions import tag

        a = np.array(3.0)
        b = np.array(5.0)

        @gradient(wrt=a)
        def f(x, y):
            return x * y
        self.assertTrue(np.allclose(f(a, 5.0), 5.0))

        @gradient(wrt=b)
        def f(x, y):
            return x * y
        self.assertRaises(ValueError, f, a, 5.0)
        self.assertTrue(np.allclose(f(a, b), 3.0))
        self.assertTrue(np.allclose(f(3.0, b), 3.0))

        @gradient(wrt=(a, b))
        def f(x, y):
            return x * y
        self.assertRaises(ValueError, f, a, 5.0)
        self.assertTrue(np.allclose(f(a, b), [5.0, 3.0]))


class TestHV(unittest.TestCase):
    def test_hv_missing_vectors(self):
        @hessian_vector
        def fn(x):
            return x
        self.assertRaises(ValueError, fn, np.array([[1, 1]]))

    def test_hv_no_scalar(self):

        @hessian_vector