That is great to hear. I appreciate your effort.
May I ask you for some further information regarding the topic of guvectorize?
I love the concept of guvectorize or numpy ufuncs in general because it enables you to write a single function that can handle arrays of varying shapes and sizes efficiently. It is very convenient as a user, it makes the code leaner and easier to maintain.
But there are limitations that restrict the flexibility and ease of use when using guvectorize compared to the jit compilation approach in Numba. guvectorize functions cannot be used within jitted functions, they produce positional arguments instead of keyword arguments, and they do not support default arguments. These limitations can impact the modularity, readability, and reusability of code that relies on guvectorize functions.
Issue 1: guvectorize functions cannot be used within jitted functions
guvectorize functions are defined as ufuncs, which are specialized functions for performing element-wise operations on arrays. These ufuncs have a different calling convention and type signature compared to regular functions, which makes it challenging for Numba to infer their types accurately. Therefore, using a guvectorize function within a jitted function results in a compilation error.
→ soon to be history 
Issue 2: positional arguments instead of keyword arguments
When using guvectorize, it produces positional arguments instead of keyword arguments. This means that the arguments need to be passed in the order defined in the function signature, and you cannot use keyword arguments to specify the values. This limitation can make the code less readable and more error-prone, especially when dealing with functions that have multiple arguments and/or optional arguments.
Issue 3: no support for default arguments
Another limitation of guvectorize is that it does not support default arguments. This limitation can be inconvenient when you want to reuse the same guvectorize function with different default values or when you want to make certain arguments optional.
I would really like to use guvectorize more often because it is a beautiful concept that is very beneficial for a user (maybe not for the developer).
Would you agree with the above mentioned description of limitations or are there already solutions in place which I may have not discovered already.
Below are some basic code snippets showing issues 1-3.
>
> import numpy as np
> import numba as nb
>
>
> @nb.guvectorize(['(float64[:], float64, float64[:])'], '(n),()->(n)')
> def add(a: np.ndarray, plus: float, out: np.ndarray):
> """Add a floating number to an array element-wise."""
> for i in range(a.size):
> out[i] = a[i] + plus
>
>
> # *********************************************************************
> # Issue 1: guvectorize function can not be used in jitted function
> # *********************************************************************
>
> @nb.jit(['float64[:](float64[:], float64)'])
> def jit_guvec(a: np.ndarray, plus: float) -> np.ndarray:
> """Use ufunc as inner func in jit."""
> return add(a, plus)
> # NumbaWarning:
> # Compilation is falling back to object mode WITH looplifting enabled because
> # Function "jit_guvec" failed type inference due to: Untyped global name 'add':
> # Cannot determine Numba type of <class 'numba.np.ufunc.gufunc.GUFunc'>
>
>
> # *********************************************************************
> # Issue 2: guvectorize produces positional instead of keyword arguments
> # *********************************************************************
>
> a = np.arange(10.)
> print(a)
> # [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
> print(add(a, 10))
> # [10. 11. 12. 13. 14. 15. 16. 17. 18. 19.]
> print(add(a=a, plus=10))
> # TypeError: add() takes from 2 to 3 positional arguments but 0 were given
>
>
> # *********************************************************************
> # Issue 3: No default arguments in guvectorize
> # *********************************************************************
>
> @nb.njit(['float64[:](float64[:], float64)',
> 'float64[:](float64[:], Omitted(None))',
> 'float64[:](float64[:], none)'])
> def add_default_njit(a: np.ndarray, plus: float = None) -> np.ndarray:
> """Create an array and add a number."""
> out = np.empty_like(a)
> plus = plus or 10.0
> for i in range(a.size):
> out[i] = a[i] + plus
> return out
>
> a = np.arange(10.)
> print(a)
> # [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
> print(add_default_njit(a, plus=100))
> # [100. 101. 102. 103. 104. 105. 106. 107. 108. 109.]
> print(add_default_njit(a, plus=None))
> # [10. 11. 12. 13. 14. 15. 16. 17. 18. 19.]
> print(add_default_njit(a))
> # [10. 11. 12. 13. 14. 15. 16. 17. 18. 19.]
> # njit is OK
>
> @nb.guvectorize([
> '(float64[:], float64, float64[:])',
> '(float64[:], Omitted(None), float64[:])',
> '(float64[:], none, float64[:])'],
> '(n),()->(n)')
> def add_default_guv(a: np.ndarray, plus: float = None, out: np.ndarray = None):
> """Create an array and add a number."""
> plus = plus or 10.0
> for i in range(a.size):
> out[i] = a[i] + plus
> # NumbaNotImplementedError: omitted(default=None) cannot be represented as a
> # NumPy dtype