###

# (NumPy)

# Ufuncs

Ufuncs or universal functions operate on ndarrays element by element, and support array broadcasting, type casting, and other features.

Lets try and see how we can use the binary and unary ufunc methods

After the neccessary includes

    #include <boost/python/numpy.hpp>
    #include <iostream>

    namespace p = boost::python;
    namespace np = boost::python::numpy;

Now we create the structs necessary to implement the ufuncs. The typedefs must be made as the ufunc generators take these typedefs as inputs and return an error otherwise

    struct UnarySquare
    {
      typedef double argument_type;
      typedef double result_type;

      double operator()(double r) const { return r * r;}
    };

    struct BinarySquare
    {
      typedef double first_argument_type;
      typedef double second_argument_type;
      typedef double result_type;

      double operator()(double a,double b) const { return (a*a + b*b) ; }
    };

Initialise the Python runtime and the numpy module

    int main(int argc, char **argv)
    {
      Py_Initialize();
      np::initialize();

Now expose the struct UnarySquare to Python as a class, and let ud be the class object.

    p::object ud = p::class_<UnarySquare, boost::shared_ptr<UnarySquare> >("UnarySquare");
    ud.def("__call__", np::unary_ufunc<UnarySquare>::make());

Let inst be an instance of the class ud

    p::object inst = ud();

Use the “__call__” method to call the overloaded () operator and print the value

    std::cout << "Square of unary scalar 1.0 is " << p::extract<char const *>(p::str(inst.attr("__call__")(1.0))) << std::endl;

Create an array in C++

    int arr[] = {1,2,3,4} ;

..and use it to create the ndarray in Python

    np::ndarray demo_array = np::from_data(arr, np::dtype::get_builtin<int>(),
                                           p::make_tuple(4),
                                           p::make_tuple(4),
                                           p::object());

Print out the demo array

    std::cout << "Demo array is " << p::extract<char const *>(p::str(demo_array)) << std::endl;

Call the “__call__” method to perform the operation and assign the value to result_array

    p::object result_array = inst.attr("__call__")(demo_array);

Print the resultant array

    std::cout << "Square of demo array is " << p::extract<char const *>(p::str(result_array)) << std::endl;

Lets try the same with a list

    p::list li;
    li.append(3);
    li.append(7);

Print out the demo list

    std::cout << "Demo list is " << p::extract<char const *>(p::str(li)) << std::endl;

Call the ufunc for the list

    result_array = inst.attr("__call__")(li);

And print the list out

    std::cout << "Square of demo list is " << p::extract<char const *>(p::str(result_array)) << std::endl;

Now lets try Binary ufuncs. Again, expose the struct BinarySquare to Python as a class, and let ud be the class object

    ud = p::class_<BinarySquare, boost::shared_ptr<BinarySquare> >("BinarySquare");
    ud.def("__call__", np::binary_ufunc<BinarySquare>::make());

And initialise ud

    inst = ud();

Print the two input lists

    std::cout << "The two input list for binary ufunc are " << std::endl
              << p::extract<char const *>(p::str(demo_array)) << std::endl
              << p::extract<char const *>(p::str(demo_array)) << std::endl;

Call the binary ufunc taking demo_array as both inputs

    result_array = inst.attr("__call__")(demo_array,demo_array);

And print the output

      std::cout << "Square of list with binary ufunc is " << p::extract<char const *>(p::str(result_array)) << std::endl;
    }