diff --git a/dpnp/backend/extensions/vm/conj.hpp b/dpnp/backend/extensions/vm/conj.hpp new file mode 100644 index 000000000000..a18226509b4c --- /dev/null +++ b/dpnp/backend/extensions/vm/conj.hpp @@ -0,0 +1,78 @@ +//***************************************************************************** +// Copyright (c) 2023, Intel Corporation +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// - Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +// THE POSSIBILITY OF SUCH DAMAGE. +//***************************************************************************** + +#pragma once + +#include + +#include "common.hpp" +#include "types_matrix.hpp" + +namespace dpnp +{ +namespace backend +{ +namespace ext +{ +namespace vm +{ +template +sycl::event conj_contig_impl(sycl::queue exec_q, + const std::int64_t n, + const char *in_a, + char *out_y, + const std::vector &depends) +{ + type_utils::validate_type_for_device(exec_q); + + const T *a = reinterpret_cast(in_a); + T *y = reinterpret_cast(out_y); + + return mkl_vm::conj(exec_q, + n, // number of elements to be calculated + a, // pointer `a` containing input vector of size n + y, // pointer `y` to the output vector of size n + depends); +} + +template +struct ConjContigFactory +{ + fnT get() + { + if constexpr (std::is_same_v< + typename types::ConjOutputType::value_type, void>) + { + return nullptr; + } + else { + return conj_contig_impl; + } + } +}; +} // namespace vm +} // namespace ext +} // namespace backend +} // namespace dpnp diff --git a/dpnp/backend/extensions/vm/types_matrix.hpp b/dpnp/backend/extensions/vm/types_matrix.hpp index ba88f192908d..e710a5b46734 100644 --- a/dpnp/backend/extensions/vm/types_matrix.hpp +++ b/dpnp/backend/extensions/vm/types_matrix.hpp @@ -83,6 +83,23 @@ struct CeilOutputType dpctl_td_ns::DefaultResultEntry>::result_type; }; +/** + * @brief A factory to define pairs of supported types for which + * MKL VM library provides support in oneapi::mkl::vm::conj function. + * + * @tparam T Type of input vector `a` and of result vector `y`. + */ +template +struct ConjOutputType +{ + using value_type = typename std::disjunction< + dpctl_td_ns:: + TypeMapResultEntry, std::complex>, + dpctl_td_ns:: + TypeMapResultEntry, std::complex>, + dpctl_td_ns::DefaultResultEntry>::result_type; +}; + /** * @brief A factory to define pairs of supported types for which * MKL VM library provides support in oneapi::mkl::vm::cos function. diff --git a/dpnp/backend/extensions/vm/vm_py.cpp b/dpnp/backend/extensions/vm/vm_py.cpp index e4f470bee3ce..da088582af13 100644 --- a/dpnp/backend/extensions/vm/vm_py.cpp +++ b/dpnp/backend/extensions/vm/vm_py.cpp @@ -33,6 +33,7 @@ #include "add.hpp" #include "ceil.hpp" #include "common.hpp" +#include "conj.hpp" #include "cos.hpp" #include "div.hpp" #include "floor.hpp" @@ -56,6 +57,7 @@ static unary_impl_fn_ptr_t ceil_dispatch_vector[dpctl_td_ns::num_types]; static unary_impl_fn_ptr_t cos_dispatch_vector[dpctl_td_ns::num_types]; static binary_impl_fn_ptr_t div_dispatch_vector[dpctl_td_ns::num_types]; static unary_impl_fn_ptr_t floor_dispatch_vector[dpctl_td_ns::num_types]; +static unary_impl_fn_ptr_t conj_dispatch_vector[dpctl_td_ns::num_types]; static unary_impl_fn_ptr_t ln_dispatch_vector[dpctl_td_ns::num_types]; static binary_impl_fn_ptr_t mul_dispatch_vector[dpctl_td_ns::num_types]; static unary_impl_fn_ptr_t sin_dispatch_vector[dpctl_td_ns::num_types]; @@ -127,6 +129,34 @@ PYBIND11_MODULE(_vm_impl, m) py::arg("sycl_queue"), py::arg("src"), py::arg("dst")); } + // UnaryUfunc: ==== Conj(x) ==== + { + vm_ext::init_ufunc_dispatch_vector( + conj_dispatch_vector); + + auto conj_pyapi = [&](sycl::queue exec_q, arrayT src, arrayT dst, + const event_vecT &depends = {}) { + return vm_ext::unary_ufunc(exec_q, src, dst, depends, + conj_dispatch_vector); + }; + m.def("_conj", conj_pyapi, + "Call `conj` function from OneMKL VM library to compute " + "conjugate of vector elements", + py::arg("sycl_queue"), py::arg("src"), py::arg("dst"), + py::arg("depends") = py::list()); + + auto conj_need_to_call_pyapi = [&](sycl::queue exec_q, arrayT src, + arrayT dst) { + return vm_ext::need_to_call_unary_ufunc(exec_q, src, dst, + conj_dispatch_vector); + }; + m.def("_mkl_conj_to_call", conj_need_to_call_pyapi, + "Check input arguments to answer if `conj` function from " + "OneMKL VM library can be used", + py::arg("sycl_queue"), py::arg("src"), py::arg("dst")); + } + // UnaryUfunc: ==== Cos(x) ==== { vm_ext::init_ufunc_dispatch_vector}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE][eft_LNG][eft_LNG] = { + fmap[DPNPFuncName::DPNP_FN_CONJUGATE][eft_LNG][eft_LNG] = { eft_LNG, (void *)dpnp_copy_c_default}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE][eft_FLT][eft_FLT] = { + fmap[DPNPFuncName::DPNP_FN_CONJUGATE][eft_FLT][eft_FLT] = { eft_FLT, (void *)dpnp_copy_c_default}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE][eft_DBL][eft_DBL] = { + fmap[DPNPFuncName::DPNP_FN_CONJUGATE][eft_DBL][eft_DBL] = { eft_DBL, (void *)dpnp_copy_c_default}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE][eft_C128][eft_C128] = { + fmap[DPNPFuncName::DPNP_FN_CONJUGATE][eft_C128][eft_C128] = { eft_C128, (void *)dpnp_conjugate_c_default>}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE_EXT][eft_INT][eft_INT] = { - eft_INT, (void *)dpnp_copy_c_ext}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE_EXT][eft_LNG][eft_LNG] = { - eft_LNG, (void *)dpnp_copy_c_ext}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE_EXT][eft_FLT][eft_FLT] = { - eft_FLT, (void *)dpnp_copy_c_ext}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE_EXT][eft_DBL][eft_DBL] = { - eft_DBL, (void *)dpnp_copy_c_ext}; - fmap[DPNPFuncName::DPNP_FN_CONJIGUATE_EXT][eft_C128][eft_C128] = { - eft_C128, (void *)dpnp_conjugate_c_ext>}; - fmap[DPNPFuncName::DPNP_FN_COPY][eft_BLN][eft_BLN] = { eft_BLN, (void *)dpnp_copy_c_default}; fmap[DPNPFuncName::DPNP_FN_COPY][eft_INT][eft_INT] = { diff --git a/dpnp/dpnp_algo/dpnp_algo.pxd b/dpnp/dpnp_algo/dpnp_algo.pxd index 80f5471831d3..c59c7ced6368 100644 --- a/dpnp/dpnp_algo/dpnp_algo.pxd +++ b/dpnp/dpnp_algo/dpnp_algo.pxd @@ -68,8 +68,6 @@ cdef extern from "dpnp_iface_fptr.hpp" namespace "DPNPFuncName": # need this na DPNP_FN_CHOLESKY_EXT DPNP_FN_CHOOSE DPNP_FN_CHOOSE_EXT - DPNP_FN_CONJIGUATE - DPNP_FN_CONJIGUATE_EXT DPNP_FN_COPY DPNP_FN_COPY_EXT DPNP_FN_COPYSIGN diff --git a/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi b/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi index 6f5aa2a614c4..64003efd7acb 100644 --- a/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi +++ b/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi @@ -39,7 +39,6 @@ __all__ += [ "dpnp_absolute", "dpnp_arctan2", "dpnp_around", - "dpnp_conjugate", "dpnp_copysign", "dpnp_cross", "dpnp_cumprod", @@ -155,10 +154,6 @@ cpdef utils.dpnp_descriptor dpnp_around(utils.dpnp_descriptor x1, int decimals): return result -cpdef utils.dpnp_descriptor dpnp_conjugate(utils.dpnp_descriptor x1): - return call_fptr_1in_1out_strides(DPNP_FN_CONJIGUATE_EXT, x1) - - cpdef utils.dpnp_descriptor dpnp_copysign(utils.dpnp_descriptor x1_obj, utils.dpnp_descriptor x2_obj, object dtype=None, diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index 926aa22453d7..7735dae36349 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -47,6 +47,7 @@ "dpnp_bitwise_or", "dpnp_bitwise_xor", "dpnp_ceil", + "dpnp_conj", "dpnp_cos", "dpnp_divide", "dpnp_equal", @@ -419,20 +420,41 @@ def dpnp_ceil(x, out=None, order="K"): """ -def _call_cos(src, dst, sycl_queue, depends=None): +_conj_docstring = """ +conj(x, out=None, order='K') + +Computes conjugate for each element `x_i` for input array `x`. + +Args: + x (dpnp.ndarray): + Input array, expected to have numeric data type. + out ({None, dpnp.ndarray}, optional): + Output array to populate. Array must have the correct + shape and the expected data type. + order ("C","F","A","K", optional): memory layout of the new + output array, if parameter `out` is `None`. + Default: "K". +Return: + dpnp.ndarray: + An array containing the element-wise conjugate. + The returned array has the same data type as `x`. +""" + + +def _call_conj(src, dst, sycl_queue, depends=None): """A callback to register in UnaryElementwiseFunc class of dpctl.tensor""" if depends is None: depends = [] - if vmi._mkl_cos_to_call(sycl_queue, src, dst): - # call pybind11 extension for cos() function from OneMKL VM - return vmi._cos(sycl_queue, src, dst, depends) - return ti._cos(src, dst, sycl_queue, depends) + if vmi._mkl_conj_to_call(sycl_queue, src, dst): + # call pybind11 extension for conj() function from OneMKL VM + return vmi._conj(sycl_queue, src, dst, depends) + return ti._conj(src, dst, sycl_queue, depends) -cos_func = UnaryElementwiseFunc( - "cos", ti._cos_result_type, _call_cos, _cos_docstring +conj_func = UnaryElementwiseFunc( + "conj", ti._conj_result_type, _call_conj, _conj_docstring ) @@ -441,13 +463,42 @@ def dpnp_cos(x, out=None, order="K"): Invokes cos() function from pybind11 extension of OneMKL VM if possible. Otherwise fully relies on dpctl.tensor implementation for cos() function. + + """ + + def _call_cos(src, dst, sycl_queue, depends=None): + """A callback to register in UnaryElementwiseFunc class of dpctl.tensor""" + + if depends is None: + depends = [] + + if vmi._mkl_cos_to_call(sycl_queue, src, dst): + # call pybind11 extension for cos() function from OneMKL VM + return vmi._cos(sycl_queue, src, dst, depends) + return ti._cos(src, dst, sycl_queue, depends) + + # dpctl.tensor only works with usm_ndarray + x1_usm = dpnp.get_usm_ndarray(x) + out_usm = None if out is None else dpnp.get_usm_ndarray(out) + + func = UnaryElementwiseFunc( + "cos", ti._cos_result_type, _call_cos, _cos_docstring + ) + res_usm = func(x1_usm, out=out_usm, order=order) + return dpnp_array._create_from_usm_ndarray(res_usm) + + +def dpnp_conj(x, out=None, order="K"): """ + Invokes conj() function from pybind11 extension of OneMKL VM if possible. + Otherwise fully relies on dpctl.tensor implementation for conj() function. + """ # dpctl.tensor only works with usm_ndarray x1_usm = dpnp.get_usm_ndarray(x) out_usm = None if out is None else dpnp.get_usm_ndarray(out) - res_usm = cos_func(x1_usm, out=out_usm, order=order) + res_usm = conj_func(x1_usm, out=out_usm, order=order) return dpnp_array._create_from_usm_ndarray(res_usm) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 8b2a8c90716a..717cb9c4f526 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -622,7 +622,7 @@ def conj(self): """ - if not dpnp.issubsctype(self.dtype, dpnp.complex_): + if not dpnp.issubsctype(self.dtype, dpnp.complexfloating): return self else: return dpnp.conjugate(self) @@ -635,7 +635,7 @@ def conjugate(self): """ - if not dpnp.issubsctype(self.dtype, dpnp.complex_): + if not dpnp.issubsctype(self.dtype, dpnp.complexfloating): return self else: return dpnp.conjugate(self) diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 724cc6bb643d..a6850b0bce48 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -52,6 +52,7 @@ check_nd_call_func, dpnp_add, dpnp_ceil, + dpnp_conj, dpnp_divide, dpnp_floor, dpnp_floor_divide, @@ -362,7 +363,17 @@ def ceil( ) -def conjugate(x1, **kwargs): +def conjugate( + x, + /, + out=None, + *, + order="K", + where=True, + dtype=None, + subok=True, + **kwargs, +): """ Return the complex conjugate, element-wise. @@ -371,10 +382,22 @@ def conjugate(x1, **kwargs): For full documentation refer to :obj:`numpy.conjugate`. + Returns + ------- + out : dpnp.ndarray + The conjugate of each element of `x`. + + Limitations + ----------- + Parameters `x` is only supported as either :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`. + Parameters `where`, `dtype` and `subok` are supported with their default values. + Otherwise the function will be executed sequentially on CPU. + Input array data types are limited by supported DPNP :ref:`Data types`. + Examples -------- >>> import dpnp as np - >>> np.conjugate(1+2j) + >>> np.conjugate(np.array(1+2j)) (1-2j) >>> x = np.eye(2) + 1j * np.eye(2) @@ -384,13 +407,17 @@ def conjugate(x1, **kwargs): """ - x1_desc = dpnp.get_dpnp_descriptor( - x1, copy_when_strides=False, copy_when_nondefault_queue=False + return check_nd_call_func( + numpy.conjugate, + dpnp_conj, + x, + out=out, + where=where, + order=order, + dtype=dtype, + subok=subok, + **kwargs, ) - if x1_desc and not kwargs: - return dpnp_conjugate(x1_desc).get_pyobj() - - return call_origin(numpy.conjugate, x1, **kwargs) conj = conjugate diff --git a/tests/skipped_tests.tbl b/tests/skipped_tests.tbl index a8604e0d3187..f5f0f62c28e3 100644 --- a/tests/skipped_tests.tbl +++ b/tests/skipped_tests.tbl @@ -559,7 +559,7 @@ tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticBinary2_para tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticBinary2_param_545_{arg1=array([[1, 2, 3], [4, 5, 6]]), arg2=array([[0, 1, 2], [3, 4, 5]]), dtype=float64, name='fmod', use_dtype=False}::test_binary tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticModf::test_modf -tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticRaisesWithNumpyInput_param_1_{name='angle', nargs=1}::test_raises_with_numpy_input +tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticRaisesWithNumpyInput_param_3_{name='angle', nargs=1}::test_raises_with_numpy_input tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp2 diff --git a/tests/skipped_tests_gpu.tbl b/tests/skipped_tests_gpu.tbl index f1a403d73180..35092f3b07b8 100644 --- a/tests/skipped_tests_gpu.tbl +++ b/tests/skipped_tests_gpu.tbl @@ -12,14 +12,12 @@ tests/test_random.py::TestPermutationsTestShuffle::test_no_miss_numbers[int64] tests/test_random.py::TestPermutationsTestShuffle::test_shuffle1[lambda x: dpnp.array([])] tests/test_random.py::TestPermutationsTestShuffle::test_shuffle1[lambda x: dpnp.astype(dpnp.asarray(x), dpnp.float32)] -tests/test_sycl_queue.py::test_1in_1out[opencl:gpu:0-conjugate-data2] tests/test_sycl_queue.py::test_1in_1out[opencl:gpu:0-copy-data3] tests/test_sycl_queue.py::test_1in_1out[opencl:gpu:0-cumprod-data4] tests/test_sycl_queue.py::test_1in_1out[opencl:gpu:0-cumsum-data5] tests/test_sycl_queue.py::test_1in_1out[opencl:gpu:0-ediff1d-data7] tests/test_sycl_queue.py::test_1in_1out[opencl:gpu:0-fabs-data8] -tests/test_sycl_queue.py::test_1in_1out[level_zero:gpu:0-conjugate-data2] tests/test_sycl_queue.py::test_1in_1out[level_zero:gpu:0-copy-data3] tests/test_sycl_queue.py::test_1in_1out[level_zero:gpu:0-cumprod-data4] tests/test_sycl_queue.py::test_1in_1out[level_zero:gpu:0-cumsum-data5] @@ -735,7 +733,7 @@ tests/third_party/cupy/manipulation_tests/test_tiling.py::TestTile_param_3_{reps tests/third_party/cupy/manipulation_tests/test_tiling.py::TestTile_param_4_{reps=(2, 3)}::test_array_tile tests/third_party/cupy/manipulation_tests/test_tiling.py::TestTile_param_5_{reps=(2, 3, 4, 5)}::test_array_tile -tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticRaisesWithNumpyInput_param_1_{name='angle', nargs=1}::test_raises_with_numpy_input +tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticRaisesWithNumpyInput_param_3_{name='angle', nargs=1}::test_raises_with_numpy_input tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp2 diff --git a/tests/skipped_tests_gpu_no_fp64.tbl b/tests/skipped_tests_gpu_no_fp64.tbl index 894f9d7d9222..2aca406319dd 100644 --- a/tests/skipped_tests_gpu_no_fp64.tbl +++ b/tests/skipped_tests_gpu_no_fp64.tbl @@ -887,7 +887,7 @@ tests/third_party/cupy/logic_tests/test_content.py::TestContent::test_isnan tests/third_party/cupy/manipulation_tests/test_basic.py::TestCopytoFromScalar_param_6_{dst_shape=(), src=(1+1j)}::test_copyto -tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticUnary_param_5_{arg1=2.0, name='reciprocal'}::test_unary +tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticUnary_param_29_{arg1=2.0, name='reciprocal'}::test_unary tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticBinary_param_6_{arg1=array([[1., 2., 3.], [4., 5., 6.]], dtype=float32), arg2=array([[0., 1., 2.], [3., 4., 5.]]), name='power'}::test_binary tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticBinary_param_10_{arg1=array([[1., 2., 3.], [4., 5., 6.]], dtype=float32), arg2=array([[0, 1, 2], [3, 4, 5]], dtype=int32), name='power'}::test_binary diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 239cf887ab41..d61331ffa08c 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -250,8 +250,9 @@ def test_meshgrid(usm_type_x, usm_type_y): [ pytest.param("ceil", [-1.7, -1.5, -0.2, 0.2, 1.5, 1.7, 2.0]), pytest.param("floor", [-1.7, -1.5, -0.2, 0.2, 1.5, 1.7, 2.0]), - pytest.param("trunc", [-1.7, -1.5, -0.2, 0.2, 1.5, 1.7, 2.0]), + pytest.param("conjugate", [[1.0 + 1.0j, 0.0], [0.0, 1.0 + 1.0j]]), pytest.param("sqrt", [1.0, 3.0, 9.0]), + pytest.param("trunc", [-1.7, -1.5, -0.2, 0.2, 1.5, 1.7, 2.0]), ], ) @pytest.mark.parametrize("usm_type", list_of_usm_types, ids=list_of_usm_types) diff --git a/tests/third_party/cupy/math_tests/test_arithmetic.py b/tests/third_party/cupy/math_tests/test_arithmetic.py index de37826cd3bc..7a8cf0ffc086 100644 --- a/tests/third_party/cupy/math_tests/test_arithmetic.py +++ b/tests/third_party/cupy/math_tests/test_arithmetic.py @@ -26,7 +26,12 @@ testing.product( { "nargs": [1], - "name": ["reciprocal", "angle"], + "name": [ + "reciprocal", + "conj", + "conjugate", + "angle", + ], } ) + testing.product( @@ -68,6 +73,18 @@ def test_raises_with_numpy_input(self): @testing.parameterize( *( testing.product( + { + "arg1": ( + [ + testing.shaped_arange((2, 3), numpy, dtype=d) + for d in all_types + ] + + [0, 0.0j, 0j, 2, 2.0, 2j, True, False] + ), + "name": ["conj", "conjugate"], + } + ) + + testing.product( { "arg1": ( [