diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc744f..91e5da6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [dev] (MM/DD/YYYY) + +### Added +* Added mkl implementation for floating point data-types of `exp2`, `log2`, `fabs`, `copysign`, `nextafter`, `fmax`, `fmin` and `remainder` functions [gh-81](https://github.com/IntelPython/mkl_umath/pull/81) + ## [0.2.0] (06/DD/2025) This release updates `mkl_umath` to be aligned with both numpy-1.26.x and numpy-2.x.x. diff --git a/mkl_umath/src/mkl_umath_loops.c.src b/mkl_umath/src/mkl_umath_loops.c.src index 42e2bf3..6bee4b2 100644 --- a/mkl_umath/src/mkl_umath_loops.c.src +++ b/mkl_umath/src/mkl_umath_loops.c.src @@ -126,7 +126,7 @@ } while(0) /* for pointers p1, and p2 pointing at contiguous arrays n-elements of size s, are arrays disjoint or same - * when these conditions are not met VML functions may product incorrect output + * when these conditions are not met VML functions may produce incorrect output */ #define DISJOINT_OR_SAME(p1, p2, n, s) (((p1) == (p2)) || ((p2) + (n)*(s) < (p1)) || ((p1) + (n)*(s) < (p2)) ) @@ -219,6 +219,7 @@ divmod@c@(@type@ a, @type@ b, @type@ *modulus) ** FLOAT LOOPS ** ***************************************************************************** */ +/* TODO: Use MKL for pow, arctan2, fmod, hypot, i0 */ /**begin repeat * Float types @@ -315,12 +316,13 @@ mkl_umath_@TYPE@_exp(char **args, const npy_intp *dimensions, const npy_intp *st can_vectorize , const @type@ in1 = *(@type@ *)ip1; - ignore_fpstatus |= ((in1 == -NPY_INFINITY@A@) ? 1 : 0); + const int invalid_cases = npy_isnan(in1) || in1 == NPY_INFINITY || in1 == -NPY_INFINITY; + ignore_fpstatus |= (invalid_cases ? 1 : 0); *(@type@ *)op1 = @scalarf@(in1); - ) + ) } if(ignore_fpstatus) { - feclearexcept(FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INVALID); + feclearexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_INVALID); } } @@ -334,22 +336,33 @@ mkl_umath_@TYPE@_exp(char **args, const npy_intp *dimensions, const npy_intp *st * #scalarf = exp2f, exp2# */ -/* TODO: Use VML */ void mkl_umath_@TYPE@_exp2(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { const int contig = IS_UNARY_CONT(@type@, @type@); const int disjoint_or_same = DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)); const int can_vectorize = contig && disjoint_or_same; + int ignore_fpstatus = 0; - UNARY_LOOP_DISPATCH( - @type@, @type@ - , - can_vectorize - , - const @type@ in1 = *(@type@ *)ip1; - *(@type@ *)op1 = @scalarf@(in1); - ) + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { + ignore_fpstatus = 1; + CHUNKED_VML_CALL2(v@c@Exp2, dimensions[0], @type@, args[0], args[1]); + /* v@c@Exp2(dimensions[0], (@type@*) args[0], (@type@*) args[1]); */ + } else { + UNARY_LOOP_DISPATCH( + @type@, @type@ + , + can_vectorize + , + const @type@ in1 = *(@type@ *)ip1; + const int invalid_cases = npy_isnan(in1) || in1 == NPY_INFINITY || in1 == -NPY_INFINITY; + ignore_fpstatus |= (invalid_cases ? 1 : 0); + *(@type@ *)op1 = @scalarf@(in1); + ) + } + if(ignore_fpstatus) { + feclearexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_INVALID); + } } /**end repeat**/ @@ -460,22 +473,34 @@ mkl_umath_@TYPE@_log(char **args, const npy_intp *dimensions, const npy_intp *st * #scalarf = log2f, log2# */ -/* TODO: Use VML */ void mkl_umath_@TYPE@_log2(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { const int contig = IS_UNARY_CONT(@type@, @type@); const int disjoint_or_same = DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)); const int can_vectorize = contig && disjoint_or_same; + int ignore_fpstatus = 0; - UNARY_LOOP_DISPATCH( - @type@, @type@ - , - can_vectorize - , - const @type@ in1 = *(@type@ *)ip1; - *(@type@ *)op1 = @scalarf@(in1); - ) + if (can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) + { + ignore_fpstatus = 1; + CHUNKED_VML_CALL2(v@c@Log2, dimensions[0], @type@, args[0], args[1]); + /* v@c@Log2(dimensions[0], (@type@*) args[0], (@type@*) args[1]); */ + } else { + UNARY_LOOP_DISPATCH( + @type@, @type@ + , + can_vectorize + , + const @type@ in1 = *(@type@ *)ip1; + const int invalid_cases = in1 < 0 || in1 == 0 || npy_isnan(in1) || in1 == -NPY_INFINITY; + ignore_fpstatus |= (invalid_cases ? 1 : 0); + *(@type@ *)op1 = @scalarf@(in1); + ) + } + if(ignore_fpstatus) { + feclearexcept(FE_DIVBYZERO | FE_INVALID); + } } /**end repeat**/ @@ -957,14 +982,20 @@ mkl_umath_@TYPE@_fabs(char **args, const npy_intp *dimensions, const npy_intp *s const int disjoint_or_same = DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)); const int can_vectorize = contig && disjoint_or_same; - UNARY_LOOP_DISPATCH( - @type@, @type@ - , - can_vectorize - , - const @type@ in1 = *(@type@ *)ip1; - *(@type@ *)op1 = @scalarf@(in1); - ) + if( can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) + { + CHUNKED_VML_CALL2(v@c@Abs, dimensions[0], @type@, args[0], args[1]); + /* v@c@Abs(dimensions[0], (@type@*) args[0], (@type@*) args[1]); */ + } else { + UNARY_LOOP_DISPATCH( + @type@, @type@ + , + can_vectorize + , + const @type@ in1 = *(@type@ *)ip1; + *(@type@ *)op1 = @scalarf@(in1); + ) + } } /**end repeat**/ @@ -1230,7 +1261,6 @@ pairwise_sum_@TYPE@(char *a, npy_intp n, npy_intp stride) * #type = npy_float, npy_double# * #TYPE = FLOAT, DOUBLE# * #c = f, # - * #C = F, # * #s = s, d# * #SUPPORTED_BY_VML = 1, 1# */ @@ -1959,20 +1989,46 @@ mkl_umath_@TYPE@_spacing(char **args, const npy_intp *dimensions, const npy_intp void mkl_umath_@TYPE@_copysign(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { - BINARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - const @type@ in2 = *(@type@ *)ip2; - *((@type@ *)op1)= copysign@c@(in1, in2); +#if @SUPPORTED_BY_VML@ + const int contig = IS_BINARY_CONT(@type@, @type@); + const int disjoint_or_same1 = DISJOINT_OR_SAME(args[0], args[2], dimensions[0], sizeof(@type@)); + const int disjoint_or_same2 = DISJOINT_OR_SAME(args[1], args[2], dimensions[0], sizeof(@type@)); + const int can_vectorize = contig && disjoint_or_same1 && disjoint_or_same2; + + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { + CHUNKED_VML_CALL3(v@s@CopySign, dimensions[0], @type@, args[0], args[1], args[2]); + /* v@s@CopySign(dimensions[0], (@type@*) args[0], (@type@*) args[1], (@type@*) args[2]); */ + } else +#endif + { + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + *((@type@ *)op1)= copysign@c@(in1, in2); + } } } void mkl_umath_@TYPE@_nextafter(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { - BINARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - const @type@ in2 = *(@type@ *)ip2; - *((@type@ *)op1)= nextafter@c@(in1, in2); +#if @SUPPORTED_BY_VML@ + const int contig = IS_BINARY_CONT(@type@, @type@); + const int disjoint_or_same1 = DISJOINT_OR_SAME(args[0], args[2], dimensions[0], sizeof(@type@)); + const int disjoint_or_same2 = DISJOINT_OR_SAME(args[1], args[2], dimensions[0], sizeof(@type@)); + const int can_vectorize = contig && disjoint_or_same1 && disjoint_or_same2; + + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { + CHUNKED_VML_CALL3(v@s@NextAfter, dimensions[0], @type@, args[0], args[1], args[2]); + /* v@s@NextAfter(dimensions[0], (@type@*) args[0], (@type@*) args[1], (@type@*) args[2]); */ + } else +#endif + { + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + *((@type@ *)op1)= nextafter@c@(in1, in2); + } } } @@ -2009,40 +2065,75 @@ mkl_umath_@TYPE@_@kind@(char **args, const npy_intp *dimensions, const npy_intp /**begin repeat1 * #kind = fmax, fmin# + * #VML = Fmax, Fmin# * #OP = >=, <=# **/ void mkl_umath_@TYPE@_@kind@(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { - /* */ - if (IS_BINARY_REDUCE) { - BINARY_REDUCE_LOOP(@type@) { - const @type@ in2 = *(@type@ *)ip2; - /* Order of operations important for MSVC 2015 */ - io1 = (io1 @OP@ in2 || isnan(in2)) ? io1 : in2; +#if @SUPPORTED_BY_VML@ + const int contig = IS_BINARY_CONT(@type@, @type@); + const int disjoint_or_same1 = DISJOINT_OR_SAME(args[0], args[2], dimensions[0], sizeof(@type@)); + const int disjoint_or_same2 = DISJOINT_OR_SAME(args[1], args[2], dimensions[0], sizeof(@type@)); + const int can_vectorize = contig && disjoint_or_same1 && disjoint_or_same2; + + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { + CHUNKED_VML_CALL3(v@s@@VML@, dimensions[0], @type@, args[0], args[1], args[2]); + /* v@s@@VML@(dimensions[0], (@type@*) args[0], (@type@*) args[1], (@type@*) args[2]); */ + } else +#endif + { + if (IS_BINARY_REDUCE) { + BINARY_REDUCE_LOOP(@type@) { + const @type@ in2 = *(@type@ *)ip2; + /* Order of operations important for MSVC 2015 */ + io1 = (io1 @OP@ in2 || isnan(in2)) ? io1 : in2; + } + *((@type@ *)iop1) = io1; } - *((@type@ *)iop1) = io1; - } - else { - BINARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - const @type@ in2 = *(@type@ *)ip2; - /* Order of operations important for MSVC 2015 */ - *((@type@ *)op1) = (in1 @OP@ in2 || isnan(in2)) ? in1 : in2; + else { + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + /* Order of operations important for MSVC 2015 */ + *((@type@ *)op1) = (in1 @OP@ in2 || isnan(in2)) ? in1 : in2; + } } + feclearexcept(FE_ALL_EXCEPT); /* clear floatstatus */ } - feclearexcept(FE_ALL_EXCEPT); /* clear floatstatus */ } /**end repeat1**/ void mkl_umath_@TYPE@_remainder(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { - BINARY_LOOP { - const @type@ in1 = *(@type@ *)ip1; - const @type@ in2 = *(@type@ *)ip2; - divmod@c@(in1, in2, (@type@ *)op1); +#if @SUPPORTED_BY_VML@ + const int contig = IS_BINARY_CONT(@type@, @type@); + const int disjoint_or_same1 = DISJOINT_OR_SAME(args[0], args[2], dimensions[0], sizeof(@type@)); + const int disjoint_or_same2 = DISJOINT_OR_SAME(args[1], args[2], dimensions[0], sizeof(@type@)); + const int can_vectorize = contig && disjoint_or_same1 && disjoint_or_same2; + int ignore_fpstatus = 0; + + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { + ignore_fpstatus = 1; + CHUNKED_VML_CALL3(v@s@Remainder, dimensions[0], @type@, args[0], args[1], args[2]); + /* v@s@Remainder(dimensions[0], (@type@*) args[0], (@type@*) args[1], (@type@*) args[2]); */ + } else +#endif + { + BINARY_LOOP { + const @type@ in1 = *(@type@ *)ip1; + const @type@ in2 = *(@type@ *)ip2; + int invalid_cases = !npy_isnan(in1) && in2 == 0; + invalid_cases |= (in1 == NPY_INFINITY || in1 == -NPY_INFINITY) && !npy_isnan(in2); + invalid_cases |= (in1 != NPY_INFINITY && in1 != -NPY_INFINITY) && (in2 == NPY_INFINITY || in2 == -NPY_INFINITY); + ignore_fpstatus |= (invalid_cases ? 1 : 0); + divmod@c@(in1, in2, (@type@ *)op1); + } } + if(ignore_fpstatus) { + feclearexcept(FE_UNDERFLOW | FE_INVALID); + } } void @@ -2059,9 +2150,11 @@ void mkl_umath_@TYPE@_square(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(data)) { #if @SUPPORTED_BY_VML@ - if(IS_UNARY_CONT(@type@, @type@) && - dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD && - DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)) ) { + const int contig = IS_UNARY_CONT(@type@, @type@); + const int disjoint_or_same = DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)); + const int can_vectorize = contig && disjoint_or_same; + + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { CHUNKED_VML_CALL2(v@s@Sqr, dimensions[0], @type@, args[0], args[1]); /* v@s@Sqr(dimensions[0], (@type@*) args[0], (@type@*) args[1]); */ } else @@ -2078,9 +2171,11 @@ void mkl_umath_@TYPE@_reciprocal(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(data)) { #if @SUPPORTED_BY_VML@ - if(IS_UNARY_CONT(@type@, @type@) && - dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD && - DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)) ) { + const int contig = IS_UNARY_CONT(@type@, @type@); + const int disjoint_or_same = DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)); + const int can_vectorize = contig && disjoint_or_same; + + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { CHUNKED_VML_CALL2(v@s@Inv, dimensions[0], @type@, args[0], args[1]); /* v@s@Inv(dimensions[0], (@type@*) args[0], (@type@*) args[1]); */ } else @@ -2114,9 +2209,11 @@ void mkl_umath_@TYPE@_absolute(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { #if @SUPPORTED_BY_VML@ - if(IS_UNARY_CONT(@type@, @type@) && - dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD && - DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)) ) { + const int contig = IS_UNARY_CONT(@type@, @type@); + const int disjoint_or_same = DISJOINT_OR_SAME(args[0], args[1], dimensions[0], sizeof(@type@)); + const int can_vectorize = contig && disjoint_or_same; + + if(can_vectorize && dimensions[0] > VML_TRANSCEDENTAL_THRESHOLD) { CHUNKED_VML_CALL2(v@s@Abs, dimensions[0], @type@, args[0], args[1]); /* v@s@Abs(dimensions[0], (@type@*) args[0], (@type@*) args[1]); */ } else @@ -2162,6 +2259,7 @@ mkl_umath_@TYPE@_sign(char **args, const npy_intp *dimensions, const npy_intp *s } } +/* TODO: USE MKL */ void mkl_umath_@TYPE@_modf(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { @@ -2261,6 +2359,7 @@ mkl_umath_@TYPE@_ldexp_long(char **args, const npy_intp *dimensions, const npy_i ** COMPLEX LOOPS ** ***************************************************************************** */ +/* TODO: USE MKL for pow, exp, ln, log10, sqrt, trigonometric functions and hyperbolic functions */ #define CGE(xr,xi,yr,yi) ((xr > yr && !isnan(xi) && !isnan(yi)) \ || (xr == yr && xi >= yi)) @@ -2363,6 +2462,7 @@ pairwise_sum_@TYPE@(@ftype@ *rr, @ftype@ * ri, char * a, npy_intp n, } } +/* TODO: USE MKL */ /**begin repeat1 * arithmetic * #kind = add, subtract# @@ -2396,6 +2496,7 @@ mkl_umath_@TYPE@_@kind@(char **args, const npy_intp *dimensions, const npy_intp } /**end repeat1**/ +/* TODO: USE MKL */ void mkl_umath_@TYPE@_multiply(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { @@ -2409,6 +2510,7 @@ mkl_umath_@TYPE@_multiply(char **args, const npy_intp *dimensions, const npy_int } } +/* TODO: USE MKL */ void mkl_umath_@TYPE@_divide(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { @@ -2557,6 +2659,7 @@ mkl_umath_@TYPE@__ones_like(char **args, const npy_intp *dimensions, const npy_i } } +/* TODO: USE MKL */ void mkl_umath_@TYPE@_conjugate(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { UNARY_LOOP { @@ -2567,6 +2670,7 @@ mkl_umath_@TYPE@_conjugate(char **args, const npy_intp *dimensions, const npy_in } } +/* TODO: USE MKL */ void mkl_umath_@TYPE@_absolute(char **args, const npy_intp *dimensions, const npy_intp *steps, void *NPY_UNUSED(func)) { @@ -2591,7 +2695,6 @@ mkl_umath_@TYPE@_absolute(char **args, const npy_intp *dimensions, const npy_int *((@ftype@ *)op1) = hypot@c@(in1r, in1i); } } - if(ignore_fpstatus) { feclearexcept(FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INVALID); } diff --git a/mkl_umath/tests/test_basic.py b/mkl_umath/tests/test_basic.py index 6a8d77e..af30e67 100644 --- a/mkl_umath/tests/test_basic.py +++ b/mkl_umath/tests/test_basic.py @@ -30,53 +30,63 @@ np.random.seed(42) -def get_args(args_str): +def get_args(args_str, size, low, high): args = [] + a = np.random.uniform(low, high, size) + b = np.random.uniform(low, high, size) for s in args_str: - if s == 'f': - args.append(np.single(np.random.random_sample())) - elif s == 'd': - args.append(np.double(np.random.random_sample())) - elif s == 'F': - args.append(np.single(np.random.random_sample()) + np.single(np.random.random_sample()) * 1j) - elif s == 'D': - args.append(np.double(np.random.random_sample()) + np.double(np.random.random_sample()) * 1j) - elif s == 'i': + if s == "f": + args.append(np.single(a)) + elif s == "d": + args.append(np.double(a)) + elif s == "F": + args.append(np.single(a) + np.single(b) * 1j) + elif s == "D": + args.append(np.double(a) + np.double(b) * 1j) + elif s == "i": args.append(np.int_(np.random.randint(low=1, high=10))) - elif s == 'l': - args.append(np.dtype('long').type(np.random.randint(low=1, high=10))) - elif s == 'q': + elif s == "l": + args.append(np.dtype("long").type(np.random.randint(low=1, high=10))) + elif s == "q": args.append(np.int64(np.random.randint(low=1, high=10))) else: raise ValueError("Unexpected type specified!") return tuple(args) umaths = [i for i in dir(mu) if isinstance(getattr(mu, i), np.ufunc)] -umaths.remove('arccosh') # expects input greater than 1 -generated_cases = {} +mkl_cases = {} +fall_back_cases = {} for umath in umaths: mkl_umath = getattr(mu, umath) types = mkl_umath.types for type_ in types: - args_str = type_[:type_.find('->')] - args = get_args(args_str) - generated_cases[(umath, type_)] = args - -additional_cases = { - ('arccosh', 'f->f'): (np.single(np.random.random_sample() + 1),), - ('arccosh', 'd->d'): (np.double(np.random.random_sample() + 1),), -} + args_str = type_[:type_.find("->")] + if umath in ["arccos", "arcsin", "arctanh"]: + low = -1; high = 1 + elif umath in ["log", "log10", "log1p", "log2", "sqrt"]: + low = 0; high = 10 + elif umath == "arccosh": + low = 1; high = 10 + else: + low = -10; high = 10 + # when size > 8192, mkl is used (if supported), + # otherwise it falls back on numpy algorithm + args_mkl = get_args(args_str, 8200, low, high) + args_fall_back = get_args(args_str, 100, low, high) + mkl_cases[(umath, type_)] = args_mkl + fall_back_cases[(umath, type_)] = args_fall_back -test_cases = {**generated_cases, **additional_cases} +test_mkl = {**mkl_cases} +test_fall_back = {**fall_back_cases} def get_id(val): return val.__str__() -@pytest.mark.parametrize("case", test_cases, ids=get_id) -def test_umath(case): +@pytest.mark.parametrize("case", test_mkl, ids=get_id) +def test_mkl_umath(case): umath, _ = case - args = test_cases[case] + args = test_mkl[case] mkl_umath = getattr(mu, umath) np_umath = getattr(np, umath) @@ -85,6 +95,20 @@ def test_umath(case): assert np.allclose(mkl_res, np_res), f"Results for '{umath}': mkl_res: {mkl_res}, np_res: {np_res}" + +@pytest.mark.parametrize("case", test_fall_back, ids=get_id) +def test_fall_back_umath(case): + umath, _ = case + args = test_fall_back[case] + mkl_umath = getattr(mu, umath) + np_umath = getattr(np, umath) + + mkl_res = mkl_umath(*args) + np_res = np_umath(*args) + + assert np.allclose(mkl_res, np_res), f"Results for '{umath}': mkl_res: {mkl_res}, np_res: {np_res}" + + def test_patch(): mp.restore() assert not mp.is_patched()