Skip to content

Apply NPY_DT_NUMERIC flag where appropriate #43

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,20 @@ repos:
rev: 22.12.0
hooks:
- id: black
name: 'black for asciidtype'
files: '^asciidtype/.*\.py'
- id: black
name: 'black for metadatadtype'
files: '^metadatadtype/.*\.py'
- id: black
name: 'black for mpfdtype'
files: '^mpfdtype/.*\.py'
- id: black
name: 'black for quaddtype'
files: '^quaddtype/.*\.py'
- id: black
name: 'black for stringdtype'
files: '^stringdtype/.*\.py'
- id: black
name: 'black for unytdtype'
files: '^unytdtype/.*\.py'
4 changes: 4 additions & 0 deletions asciidtype/tests/test_asciidtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,7 @@ def test_pickle():
assert res[1] == dtype

os.remove(f.name)


def test_is_numeric():
assert not ASCIIDType._is_numeric
2 changes: 1 addition & 1 deletion metadatadtype/metadatadtype/src/dtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ init_metadata_dtype(void)
PyArrayMethod_Spec **casts = get_casts();

PyArrayDTypeMeta_Spec MetadataDType_DTypeSpec = {
.flags = NPY_DT_PARAMETRIC,
.flags = NPY_DT_PARAMETRIC | NPY_DT_NUMERIC,
.casts = casts,
.typeobj = MetadataScalar_Type,
.slots = MetadataDType_Slots,
Expand Down
6 changes: 5 additions & 1 deletion metadatadtype/tests/test_metadatadtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ def test_cast_to_float64():
dtype = MetadataDType("test")
scalar = MetadataScalar(1, dtype)
arr = np.array([scalar, scalar, scalar])
conv = arr.astype('float64')
conv = arr.astype("float64")
assert str(conv) == "[1. 1. 1.]"


def test_is_numeric():
assert MetadataDType._is_numeric
86 changes: 30 additions & 56 deletions mpfdtype/mpfdtype/src/dtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
#include "casts.h"
#include "dtype.h"



/*
* Internal helper to create new instances.
*/
Expand All @@ -27,9 +25,8 @@ new_MPFDType_instance(mpfr_prec_t precision)
* set in that case.
*/
if (precision < MPFR_PREC_MIN || precision > MPFR_PREC_MAX) {
PyErr_Format(PyExc_ValueError,
"precision must be between %d and %d.",
MPFR_PREC_MIN, MPFR_PREC_MAX);
PyErr_Format(PyExc_ValueError, "precision must be between %d and %d.", MPFR_PREC_MIN,
MPFR_PREC_MAX);
return NULL;
}

Expand All @@ -43,7 +40,7 @@ new_MPFDType_instance(mpfr_prec_t precision)
size_t size = mpfr_custom_get_size(precision);
if (size > NPY_MAX_INT - sizeof(mpf_field)) {
PyErr_SetString(PyExc_TypeError,
"storage of single float would be too large for precision.");
"storage of single float would be too large for precision.");
}
new->base.elsize = sizeof(mpf_storage) + size;
new->base.alignment = _Alignof(mpf_field);
Expand All @@ -52,15 +49,13 @@ new_MPFDType_instance(mpfr_prec_t precision)
return new;
}


static MPFDTypeObject *
ensure_canonical(MPFDTypeObject *self)
{
Py_INCREF(self);
return self;
}


static MPFDTypeObject *
common_instance(MPFDTypeObject *dtype1, MPFDTypeObject *dtype2)
{
Expand All @@ -74,17 +69,15 @@ common_instance(MPFDTypeObject *dtype1, MPFDTypeObject *dtype2)
}
}


static PyArray_DTypeMeta *
common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
{
/*
* Typenum is useful for NumPy, but there it can still be convenient.
* (New-style user dtypes will probably get -1 as type number...)
*/
if (other->type_num >= 0
&& PyTypeNum_ISNUMBER(other->type_num)
&& !PyTypeNum_ISCOMPLEX(other->type_num)) {
if (other->type_num >= 0 && PyTypeNum_ISNUMBER(other->type_num) &&
!PyTypeNum_ISCOMPLEX(other->type_num)) {
/*
* A (simple) builtin numeric type (not complex) promotes to fixed
* precision.
Expand All @@ -96,18 +89,15 @@ common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
return (PyArray_DTypeMeta *)Py_NotImplemented;
}


/*
* Functions dealing with scalar logic
*/

static PyArray_Descr *
mpf_discover_descriptor_from_pyobject(
PyArray_DTypeMeta *NPY_UNUSED(cls), PyObject *obj)
mpf_discover_descriptor_from_pyobject(PyArray_DTypeMeta *NPY_UNUSED(cls), PyObject *obj)
{
if (Py_TYPE(obj) != &MPFloat_Type) {
PyErr_SetString(PyExc_TypeError,
"Can only store MPFloat in a MPFDType array.");
PyErr_SetString(PyExc_TypeError, "Can only store MPFloat in a MPFDType array.");
return NULL;
}
mpfr_prec_t prec = get_prec_from_object(obj);
Expand All @@ -117,7 +107,6 @@ mpf_discover_descriptor_from_pyobject(
return (PyArray_Descr *)new_MPFDType_instance(prec);
}


static int
mpf_setitem(MPFDTypeObject *descr, PyObject *obj, char *dataptr)
{
Expand Down Expand Up @@ -167,18 +156,14 @@ mpf_getitem(MPFDTypeObject *descr, char *dataptr)
return (PyObject *)new;
}


static PyType_Slot MPFDType_Slots[] = {
{NPY_DT_ensure_canonical, &ensure_canonical},
{NPY_DT_common_instance, &common_instance},
{NPY_DT_common_dtype, &common_dtype},
{NPY_DT_discover_descr_from_pyobject,
&mpf_discover_descriptor_from_pyobject},
{NPY_DT_setitem, &mpf_setitem},
{NPY_DT_getitem, &mpf_getitem},
{0, NULL}
};

{NPY_DT_ensure_canonical, &ensure_canonical},
{NPY_DT_common_instance, &common_instance},
{NPY_DT_common_dtype, &common_dtype},
{NPY_DT_discover_descr_from_pyobject, &mpf_discover_descriptor_from_pyobject},
{NPY_DT_setitem, &mpf_setitem},
{NPY_DT_getitem, &mpf_getitem},
{0, NULL}};

/*
* The following defines everything type object related (i.e. not NumPy
Expand All @@ -195,59 +180,49 @@ MPFDType_new(PyTypeObject *NPY_UNUSED(cls), PyObject *args, PyObject *kwds)

Py_ssize_t precision;

if (!PyArg_ParseTupleAndKeywords(
args, kwds, "n:MPFDType", kwargs_strs, &precision)) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "n:MPFDType", kwargs_strs, &precision)) {
return NULL;
}

return (PyObject *)new_MPFDType_instance(precision);
}


static PyObject *
MPFDType_repr(MPFDTypeObject *self)
{
PyObject *res = PyUnicode_FromFormat(
"MPFDType(%ld)", (long)self->precision);
PyObject *res = PyUnicode_FromFormat("MPFDType(%ld)", (long)self->precision);
return res;
}


PyObject *
MPFDType_get_prec(MPFDTypeObject *self)
{
return PyLong_FromLong(self->precision);
}


NPY_NO_EXPORT PyGetSetDef mpfdtype_getsetlist[] = {
{"prec",
(getter)MPFDType_get_prec,
NULL,
NULL, NULL},
{NULL, NULL, NULL, NULL, NULL}, /* Sentinel */
{"prec", (getter)MPFDType_get_prec, NULL, NULL, NULL},
{NULL, NULL, NULL, NULL, NULL}, /* Sentinel */
};


/*
* This is the basic things that you need to create a Python Type/Class in C.
* However, there is a slight difference here because we create a
* PyArray_DTypeMeta, which is a larger struct than a typical type.
* (This should get a bit nicer eventually with Python >3.11.)
*/
PyArray_DTypeMeta MPFDType = {{{
PyVarObject_HEAD_INIT(NULL, 0)
.tp_name = "MPFDType.MPFDType",
.tp_basicsize = sizeof(MPFDTypeObject),
.tp_new = MPFDType_new,
.tp_repr = (reprfunc)MPFDType_repr,
.tp_str = (reprfunc)MPFDType_repr,
.tp_getset = mpfdtype_getsetlist,
}},
/* rest, filled in during DTypeMeta initialization */
PyArray_DTypeMeta MPFDType = {
{{
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "MPFDType.MPFDType",
.tp_basicsize = sizeof(MPFDTypeObject),
.tp_new = MPFDType_new,
.tp_repr = (reprfunc)MPFDType_repr,
.tp_str = (reprfunc)MPFDType_repr,
.tp_getset = mpfdtype_getsetlist,
}},
/* rest, filled in during DTypeMeta initialization */
};


int
init_mpf_dtype(void)
{
Expand All @@ -258,7 +233,7 @@ init_mpf_dtype(void)
PyArrayMethod_Spec **casts = init_casts();

PyArrayDTypeMeta_Spec MPFDType_DTypeSpec = {
.flags = NPY_DT_PARAMETRIC,
.flags = NPY_DT_PARAMETRIC | NPY_DT_NUMERIC,
.casts = casts,
.typeobj = &MPFloat_Type,
.slots = MPFDType_Slots,
Expand All @@ -271,8 +246,7 @@ init_mpf_dtype(void)
return -1;
}

if (PyArrayInitDTypeMeta_FromSpec(
&MPFDType, &MPFDType_DTypeSpec) < 0) {
if (PyArrayInitDTypeMeta_FromSpec(&MPFDType, &MPFDType_DTypeSpec) < 0) {
free_casts();
return -1;
}
Expand Down
10 changes: 5 additions & 5 deletions mpfdtype/mpfdtype/tests/test_array.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import pytest

import sys
import numpy as np
import operator
from numpy.testing import assert_array_equal

from mpfdtype import MPFDType, MPFloat
from mpfdtype import MPFDType


def test_advanced_indexing():
Expand All @@ -16,3 +12,7 @@ def test_advanced_indexing():
b = arr[[1, 2, 3, 4]]
b[...] = 5 # does not mutate arr (internal references not broken)
assert_array_equal(arr, orig)


def test_is_numeric():
assert MPFDType._is_numeric
1 change: 1 addition & 0 deletions quaddtype/quaddtype/src/dtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ init_quad_dtype(void)
};

PyArrayDTypeMeta_Spec QuadDType_DTypeSpec = {
.flags = NPY_DT_NUMERIC,
.casts = casts,
.typeobj = QuadScalar_Type,
.slots = QuadDType_Slots,
Expand Down
13 changes: 9 additions & 4 deletions quaddtype/tests/test_quaddtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@ def test_scalar_creation():


def test_create_with_explicit_dtype():
assert repr(
np.array([3.0, 3.1, 3.2], dtype=QuadDType())
) == "array([3.0, 3.1, 3.2], dtype=This is a quad (128-bit float) dtype.)"
assert (
repr(np.array([3.0, 3.1, 3.2], dtype=QuadDType()))
== "array([3.0, 3.1, 3.2], dtype=This is a quad (128-bit float) dtype.)"
)


def test_multiply():
x = np.array([3, 8.0], dtype=QuadDType())
assert str(x * x) == '[9.0 64.0]'
assert str(x * x) == "[9.0 64.0]"


def test_bytes():
"""Check that each quad is 16 bytes."""
x = np.array([3, 8.0, 1.4], dtype=QuadDType())
assert len(x.tobytes()) == x.size * 16


def test_is_numeric():
assert QuadDType._is_numeric
4 changes: 4 additions & 0 deletions stringdtype/tests/test_stringdtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,7 @@ def test_creation_functions():

# make sure getitem works too
assert np.empty(3, dtype=StringDType())[0] == ""


def test_is_numeric():
assert not StringDType._is_numeric
8 changes: 6 additions & 2 deletions unytdtype/tests/test_unytdtype.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def test_dtype_creation():
dtype = UnytDType("m")
assert str(dtype) == "UnytDType('m')"

dtype2 = UnytDType(unyt.Unit('m'))
dtype2 = UnytDType(unyt.Unit("m"))
assert str(dtype2) == "UnytDType('m')"
assert dtype == dtype2

Expand Down Expand Up @@ -69,5 +69,9 @@ def test_insert_with_different_unit():
def test_cast_to_float64():
meter = UnytScalar(1, unyt.m)
arr = np.array([meter, meter, meter])
conv = arr.astype('float64')
conv = arr.astype("float64")
assert str(conv) == "[1. 1. 1.]"


def test_is_numeric():
assert UnytDType._is_numeric
2 changes: 1 addition & 1 deletion unytdtype/unytdtype/src/dtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ init_unyt_dtype(void)
PyArrayMethod_Spec **casts = get_casts();

PyArrayDTypeMeta_Spec UnytDType_DTypeSpec = {
.flags = NPY_DT_PARAMETRIC,
.flags = (NPY_DT_PARAMETRIC | NPY_DT_NUMERIC),
.casts = casts,
.typeobj = UnytScalar_Type,
.slots = UnytDType_Slots,
Expand Down