From 2fe82af37e342b1916f6faf5da347ac2859263a1 Mon Sep 17 00:00:00 2001 From: drculhane <160938282+drculhane@users.noreply.github.com> Date: Thu, 14 Nov 2024 13:49:07 -0500 Subject: [PATCH] Closes 3813 and 3866 -- moves several new functions to the new interface (abs, square, all exp and log, isnan, isinf, isfinite) (#3873) * Ready for upload * Fixes error in test_square * another test_square bug fix * Addresses comments * Update registration-config.json to 1 dim --------- Co-authored-by: drculhane --- arkouda/numpy/_numeric.py | 73 +++++++++----------- src/EfuncMsg.chpl | 134 ++++++++++++------------------------ tests/numpy/numeric_test.py | 41 +++++++++-- 3 files changed, 113 insertions(+), 135 deletions(-) diff --git a/arkouda/numpy/_numeric.py b/arkouda/numpy/_numeric.py index 9a83fd7ffa..20ccf6fb9a 100644 --- a/arkouda/numpy/_numeric.py +++ b/arkouda/numpy/_numeric.py @@ -249,10 +249,9 @@ def abs(pda: pdarray) -> pdarray: array([5, 4, 3, 2, 1]) """ repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"abs<{pda.dtype},{pda.ndim}>", args={ - "func": "abs", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @@ -456,10 +455,9 @@ def isfinite(pda: pdarray) -> pdarray: array([True, True, False]) """ repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"isfinite<{pda.ndim}>", args={ - "func": "isfinite", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @@ -493,10 +491,9 @@ def isinf(pda: pdarray) -> pdarray: array([False, False, True]) """ repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"isinf<{pda.ndim}>", args={ - "func": "isinf", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @@ -539,10 +536,9 @@ def isnan(pda: pdarray) -> pdarray: raise TypeError("isnan only supports pdarray of numeric type.") repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"isnan<{pda.ndim}>", args={ - "func": "isnan", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @@ -586,82 +582,78 @@ def log(pda: pdarray) -> pdarray: array([0, 3.3219280948873626, 6.6438561897747253]) """ repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"log<{pda.dtype},{pda.ndim}>", args={ - "func": "log", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @typechecked -def log10(x: pdarray) -> pdarray: +def log10(pda: pdarray) -> pdarray: """ Return the element-wise base 10 log of the array. Parameters __________ - x : pdarray - array to compute on + pda : pdarray + array to compute on Returns _______ pdarray contain values of the base 10 log """ repMsg = generic_msg( - cmd=f"efunc{x.ndim}D", + cmd=f"log10<{pda.dtype},{pda.ndim}>", args={ - "func": "log10", - "array": x, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @typechecked -def log2(x: pdarray) -> pdarray: +def log2(pda: pdarray) -> pdarray: """ Return the element-wise base 2 log of the array. Parameters __________ - x : pdarray - array to compute on + pda : pdarray + array to compute on Returns _______ pdarray contain values of the base 2 log """ repMsg = generic_msg( - cmd=f"efunc{x.ndim}D", + cmd=f"log2<{pda.dtype},{pda.ndim}>", args={ - "func": "log2", - "array": x, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @typechecked -def log1p(x: pdarray) -> pdarray: +def log1p(pda: pdarray) -> pdarray: """ Return the element-wise natural log of one plus the array. Parameters __________ - x : pdarray - array to compute on + pda : pdarray + array to compute on Returns _______ pdarray contain values of the natural log of one plus the array """ repMsg = generic_msg( - cmd=f"efunc{x.ndim}D", + cmd=f"log1p<{pda.dtype},{pda.ndim}>", args={ - "func": "log1p", - "array": x, + "pda": pda, }, ) return create_pdarray(repMsg) @@ -697,10 +689,9 @@ def exp(pda: pdarray) -> pdarray: 33.494295836924771, 13.478894913238722]) """ repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"exp<{pda.dtype},{pda.ndim}>", args={ - "func": "exp", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @@ -736,10 +727,9 @@ def expm1(pda: pdarray) -> pdarray: 32.494295836924771, 12.478894913238722]) """ repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"expm1<{pda.dtype},{pda.ndim}>", args={ - "func": "expm1", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) @@ -771,10 +761,9 @@ def square(pda: pdarray) -> pdarray: array([1, 4, 9, 16]) """ repMsg = generic_msg( - cmd=f"efunc{pda.ndim}D", + cmd=f"square<{pda.dtype},{pda.ndim}>", args={ - "func": "square", - "array": pda, + "pda": pda, }, ) return create_pdarray(type_cast(str, repMsg)) diff --git a/src/EfuncMsg.chpl b/src/EfuncMsg.chpl index 44e8f56c19..63cb2311e9 100644 --- a/src/EfuncMsg.chpl +++ b/src/EfuncMsg.chpl @@ -51,85 +51,95 @@ module EfuncMsg proc sine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return sin(x); } - proc sine (x : [?d] ?t) : [d] real throws - { throw new Error ("sin does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="cos") proc cosine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return cos(x); } - proc cosine (x : [?d] ?t) : [d] real throws - { throw new Error ("cos does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="tan") proc tangent (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return tan(x); } - proc tangent (x : [?d] ?t) : [d] real throws - { throw new Error ("tan does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="arcsin") proc arcsine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return asin(x); } - proc arcsine (x : [?d] ?t) : [d] real throws - { throw new Error ("arcsin does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="arccos") proc arccosine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return acos(x); } - proc arccosine (x : [?d] ?t) : [d] real throws - { throw new Error ("arccos does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="arctan") proc arctangent (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return atan(x); } - proc arctangent (x : [?d] ?t) : [d] real throws - { throw new Error ("arctan does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="sinh") proc hypsine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return sinh(x); } - proc hypsine (x : [?d] ?t) : [d] real throws - { throw new Error ("sinh does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="cosh") proc hypcosine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return cosh(x); } - proc hypcosine (x : [?d] ?t) : [d] real throws - { throw new Error ("cosh does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="tanh") proc hyptangent (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return tanh(x); } - proc hyptangent (x : [?d] ?t) : [d] real throws - { throw new Error ("tanh does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="arcsinh") proc archypsine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return asinh(x); } - proc archypsine (x : [?d] ?t) : [d] real throws - { throw new Error ("arcsinh does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="arccosh") proc archypcosine (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return acosh(x); } - proc archypcosine (x : [?d] ?t) : [d] real throws - { throw new Error ("arccosh does not support type %s".format(type2str(t))) ; } - @arkouda.registerCommand (name="arctanh") proc archyptangent (x : [?d] ?t) : [d] real throws where (t==int || t==real || t==uint) { return atanh(x); } - proc archyptangent (x : [?d] ?t) : [d] real throws - { throw new Error ("arctanh does not support type %s".format(type2str(t))) ; } +// This sections adds abs, square, all the exp and log functions, and the boolean "is" functions. + + @arkouda.registerCommand(name="abs") + proc absolut (const ref pda : [?d] ?t) : [d] t throws + where (t==int || t==real) { return abs(pda) ; } // TODO maybe: allow uint and return pda + + @arkouda.registerCommand(name="square") + proc boxy (const ref pda : [?d] ?t) : [d] t throws + where (t==int || t==real || t==uint) { return square(pda) ; } + + @arkouda.registerCommand(name="exp") + proc expo (const ref pda : [?d] ?t) : [d] real throws + where (t==int || t==real || t==uint) { return exp(pda) ; } + + @arkouda.registerCommand(name="expm1") + proc expom (const ref pda : [?d] ?t) : [d] real throws + where (t==int || t==real || t==uint) { return expm1(pda) ; } + + @arkouda.registerCommand(name="log") + proc log_e (const ref pda : [?d] ?t) : [d] real throws + where (t==int || t==real || t==uint) { return log(pda) ; } + + @arkouda.registerCommand(name="log1p") + proc log_1p (const ref pda : [?d] ?t) : [d] real throws + where (t==int || t==real || t==uint) { return log1p(pda) ; } + +// chapel log2 returns ints when given ints, so the input has been cast to real. + + @arkouda.registerCommand(name="log2") + proc log_2 (const ref pda : [?d] ?t) : [d] real throws + where (t==int || t==real || t==uint) { return log2(pda:real) ; } + + @arkouda.registerCommand(name="log10") + proc log_10 (const ref pda : [?d] ?t) : [d] real throws + where (t==int || t==real || t==uint) { return log10(pda) ; } + + @arkouda.registerCommand(name="isinf") + proc isinf_ (pda : [?d] real) : [d] bool { return (isInf(pda)) ; } + + @arkouda.registerCommand(name="isnan") + proc isnan_ (pda : [?d] real) : [d] bool { return (isNan(pda)) ; } + + @arkouda.registerCommand(name="isfinite") + proc isfinite_ (pda : [?d] real) : [d] bool { return (isFinite(pda)) ; } + // End of rewrite section -- delete this comment after all of EfuncMsg is rewritten. @@ -152,24 +162,12 @@ module EfuncMsg ref ea = e.a; select efunc { - when "abs" { - st.addEntry(rname, new shared SymEntry(abs(ea))); - } - when "log" { - st.addEntry(rname, new shared SymEntry(log(ea))); - } when "round" { st.addEntry(rname, new shared SymEntry(ea)); } when "sgn" { st.addEntry(rname, new shared SymEntry(sgn(ea))); } - when "exp" { - st.addEntry(rname, new shared SymEntry(exp(ea))); - } - when "square" { - st.addEntry(rname, new shared SymEntry(square(ea))); - } when "cumsum" { if nd == 1 { // check there's enough room to create a copy for scan and throw if creating a copy would go over memory limit @@ -238,9 +236,6 @@ module EfuncMsg ref ea = e.a; select efunc { - when "abs" { - st.addEntry(rname, new shared SymEntry(abs(ea))); - } when "ceil" { st.addEntry(rname, new shared SymEntry(ceil(ea))); } @@ -256,36 +251,6 @@ module EfuncMsg when "sgn" { st.addEntry(rname, new shared SymEntry(sgn(ea))); } - when "isfinite" { - st.addEntry(rname, new shared SymEntry(isFinite(ea))); - } - when "isinf" { - st.addEntry(rname, new shared SymEntry(isInf(ea))); - } - when "isnan" { - st.addEntry(rname, new shared SymEntry(isNan(ea))); - } - when "log" { - st.addEntry(rname, new shared SymEntry(log(ea))); - } - when "log1p" { - st.addEntry(rname, new shared SymEntry(log1p(ea))); - } - when "log2" { - st.addEntry(rname, new shared SymEntry(log2(ea))); - } - when "log10" { - st.addEntry(rname, new shared SymEntry(log10(ea))); - } - when "exp" { - st.addEntry(rname, new shared SymEntry(exp(ea))); - } - when "expm1" { - st.addEntry(rname, new shared SymEntry(expm1(ea))); - } - when "square" { - st.addEntry(rname, new shared SymEntry(square(ea))); - } when "cumsum" { if nd == 1 { // check there's enough room to create a copy for scan and throw if creating a copy would go over memory limit @@ -438,15 +403,6 @@ module EfuncMsg // code append second array's attrib repMsg += "created " + st.attrib(rname2) + "+"; } - when "log" { - st.addEntry(rname, new shared SymEntry(log(ea))); - } - when "exp" { - st.addEntry(rname, new shared SymEntry(exp(ea))); - } - when "square" { - st.addEntry(rname, new shared SymEntry(square(ea))); - } when "not" { st.addEntry(rname, new shared SymEntry(!e.a)); } diff --git a/tests/numpy/numeric_test.py b/tests/numpy/numeric_test.py index 85fcf469d6..e6b80b1da7 100644 --- a/tests/numpy/numeric_test.py +++ b/tests/numpy/numeric_test.py @@ -2,9 +2,11 @@ import numpy as np import pytest +import warnings import arkouda as ak from arkouda.client import get_max_array_rank +from arkouda.testing import assert_almost_equivalent as ak_assert_almost_equivalent from arkouda.numpy.dtypes import dtype as akdtype from arkouda.numpy.dtypes import str_ @@ -344,12 +346,16 @@ def test_histogram_multidim(self, num_type1, num_type2): assert np.allclose(np_edge.tolist(), ak_edge.to_list()) @pytest.mark.parametrize("num_type", NO_BOOL) - def test_log_and_exp(self, num_type): + @pytest.mark.parametrize("op", ["exp", "log", "expm1", "log2", "log10", "log1p"]) + def test_log_and_exp(self, num_type, op): na = np.linspace(1, 10, 10).astype(num_type) pda = ak.array(na, dtype=num_type) - for npfunc, akfunc in ((np.log, ak.log), (np.exp, ak.exp)): - assert np.allclose(npfunc(na), akfunc(pda).to_ndarray()) + akfunc = getattr(ak,op) + npfunc = getattr(np, op) + + ak_assert_almost_equivalent(akfunc(pda), npfunc(na)) + with pytest.raises(TypeError): akfunc(np.array([range(0, 10)]).astype(num_type)) @@ -368,6 +374,19 @@ def test_abs(self, num_type): with pytest.raises(TypeError): ak.abs(np.array([range(0, 10)]).astype(num_type)) + @pytest.mark.parametrize("num_type", NO_BOOL) + @pytest.mark.parametrize("prob_size", pytest.prob_size) + def test_square(self, prob_size, num_type): + nda = np.arange(prob_size).astype(num_type) + if num_type != ak.uint64 : + nda = nda - prob_size//2 + pda = ak.array(nda) + + assert np.allclose(np.square(nda), ak.square(pda).to_ndarray()) + + with pytest.raises(TypeError): + ak.square(np.array([range(-10, 10)]).astype(ak.bool_)) + @pytest.mark.parametrize("num_type1", NO_BOOL) @pytest.mark.parametrize("num_type2", NO_BOOL) def test_dot(self, num_type1, num_type2): @@ -549,7 +568,7 @@ def test_value_counts_error(self): def test_isnan(self): """ - Test efunc `isnan`; it returns a pdarray of element-wise T/F values for whether it is NaN + Test isnan; it returns a pdarray of element-wise T/F values for whether it is NaN (not a number) """ npa = np.array([1, 2, None, 3, 4], dtype="float64") @@ -565,6 +584,20 @@ def test_isnan(self): with pytest.raises(TypeError): ak.isnan(ark_s_string) + def test_isinf_isfinite(self) : + """ + Test isinf and isfinite. These return pdarrays of T/F values as appropriate. + """ + nda = np.array([0,9999.9999]) + pda = ak.array(nda) + warnings.filterwarnings("ignore") + nda_blowup = np.exp(nda) + warnings.filterwarnings("default") + pda_blowup = ak.exp(pda) + assert (np.isinf(nda_blowup) == ak.isinf(pda_blowup).to_ndarray()).all() + assert (np.isfinite(nda_blowup) == ak.isfinite(pda_blowup).to_ndarray()).all() + + def test_str_cat_cast(self): test_strs = [ ak.array([f"str {i}" for i in range(101)]),