When a backend framework is set by calling
ivy.set_backend(backend_name), then all Ivy functions are wrapped.
This is achieved by calling _wrap_function, which will apply the appropriate wrapping to the given function, based on what decorators it has.
For example, abs has the decorators
@handle_out_argument, and so the backend implementations will also be wrapped with the to_native_arrays_and_back and handle_out_argument wrappers.
The new function returned by
_wrap_function is a replacement of the original function with extra code added to support requirements common to many functions in the API.
This is the main purpose of the wrapping, to avoid code duplication which would exist if we added identical logic in every single function independently.
Our test decorators actually transforms to
@given decorators at Pytest collecting time, therefore this allows us to use other Hypothesis decorators like,
The order in which Ivy decorators are applied is important. It is important to follow this order, as the functionality of many functions depends on it. If the decorators are applied in the wrong order, the test may fail or the function may not behave as expected. The following is the recommended order to follow :
This recommended order is followed to ensure that tests are efficient and accurate. It is important to follow this order because the decorators depend on each other. For example, the
@infer_device decorator needs to be applied before the
@infer_dtype decorator, because the
@infer_dtype decorator needs to know the device of the function in order to infer the data type.
to_native_arrays_and_back : This wrapping function converts all
ivy.Arrayinstances in the arguments to their
ivy.NativeArraycounterparts, calls the function with those arguments and then converts the
ivy.NativeArrayinstances in the output back to
ivy.Array. This wrapping function is heavily used because it enables achieving the objective of ensuring that every ivy function could accept an
ivy.Arrayand return an
ivy.Array, making it independent of the Backend Setting.
infer_dtype : This wrapping function infers the dtype argument to be passed to a function based on the array arguments passed to it. If
dtypeis explicitly passed to the function, then it is used directly. This wrapping function could be found in functions from the creation submodule such as zeros where we then allow the user to not enter the
dtypeargument to such functions.
infer_device : Similar to the infer_dtype wrapping function, the infer_device function wrapping infers the
deviceargument to be passed to a function based on the first array argument passed to it. This wrapping function is also used a lot in functions from the creation submodule such as asarray, where we want to create the ivy.Array on the same device as the input array.
Out Argument Support#
handle_out_argument : This wrapping function is used in nearly all ivy functions. It enables appropriate handling of the
outargument of functions. In cases where the backend framework natively supports the
outargument for a function, we prefer to use it as it’s a more efficient implementation of the
outargument for that particular backend framework. But in cases when it isn’t supported, we support it anyway with Inplace Updates.
handle_nestable : This wrapping function enables the use of
ivy.Containerarguments in functions and directly calling them through the
ivynamespace, just like calling a function with
ivy.Arrayarguments instead. Thus, the function can be called by passing an
ivy.Containerto any or all of its arguments.
Partial Mixed Function Support#
The condition is specified through a lambda function which when evaluates to True the primary implementation is run and otherwise the compositional implementation is executed.
For backends that have a primary implementation of a mixed function, the reference to the compositional implementation is stored as an attribute inside the backend function during backend setting. To make use of this decorator, one must
partial_mixed_handlerattribute containing the lambda function to the backend implementation. Here’s an example from the torch backend implementation of linear.
to_native_shapes_and_back : This wrapping function converts all
ivy.Shapeinstances in the arguments to their
ivy.NativeShapecounterparts, calls the function with those arguments and then converts the
ivy.NativeShapeinstances in the output back to
handle_complex_input : This wrapping function enables handling of complex numbers. It introduces a keyword argument
complex_mode, which is used to choose the function’s behaviour as per the wrapper’s docstring.
When calling _wrap_function during Backend Setting, firstly the attributes of the functions are checked to get all the wrapping functions for a particular function. Then all the wrapping functions applicable to a function are used to wrap the function.
Each of these topics and each associated piece of logic added by the various wrapper functions are covered in more detail in the next sections. For now, suffice it to say that they do quite a lot.
This should have hopefully given you a good feel for how function wrapping is applied to functions in Ivy.