Skip to content

Commit 30d7137

Browse files
authored
Merge pull request #10 from ngoldbaum/add-asciidtype
Add asciidtype
2 parents 7fa8299 + 6be8300 commit 30d7137

File tree

20 files changed

+728
-0
lines changed

20 files changed

+728
-0
lines changed

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ jobs:
2020
run: |
2121
pip install -i https://pypi.anaconda.org/scipy-wheels-nightly/simple numpy
2222
python -m pip install -U pip build pytest unyt wheel meson ninja meson-python patchelf
23+
- name: Install asciidtype
24+
working-directory: asciidtype
25+
run: |
26+
CFLAGS="-Werror" python -m pip install . --no-build-isolation
27+
- name: Run asciidtype tests
28+
working-directory: asciidtype
29+
run: |
30+
pytest -vvv --color=yes
2331
- name: Install metadatadtype
2432
working-directory: metadatadtype
2533
run: |

.pre-commit-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ repos:
99
entry: |
1010
bash -c 'cd metadatadtype && mkdir -p build && pip install build meson-python patchelf wheel && python -m build --wheel --no-isolation -Cbuilddir=build';
1111
fail_fast: false
12+
- id: generate-compilation-database-asciidtype
13+
name: Generate compilation database [asciidtype]
14+
files: asciidtype/(meson\.build$|.*\.(c|h)$)
15+
language: system
16+
require_serial: true
17+
entry: |
18+
bash -c 'cd asciidtype && mkdir -p build && pip install build meson-python patchelf wheel && python -m build --wheel --no-isolation -Cbuilddir=build';
19+
fail_fast: false
1220
- id: generate-compilation-database-quaddtype
1321
name: Generate compilation database [quaddtype]
1422
files: quaddtype/(meson\.build$|.*\.(c|h)$)

asciidtype/.clang-format

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# A clang-format style that approximates Python's PEP 7
2+
# Useful for IDE integration
3+
#
4+
# Based on Paul Ganssle's version at
5+
# https://gist.github.com/pganssle/0e3a5f828b4d07d79447f6ced8e7e4db
6+
# and modified for NumPy
7+
BasedOnStyle: Google
8+
AlignAfterOpenBracket: Align
9+
AllowShortEnumsOnASingleLine: false
10+
AllowShortIfStatementsOnASingleLine: false
11+
AlwaysBreakAfterReturnType: TopLevel
12+
BreakBeforeBraces: Stroustrup
13+
ColumnLimit: 79
14+
ContinuationIndentWidth: 8
15+
DerivePointerAlignment: false
16+
IndentWidth: 4
17+
IncludeBlocks: Regroup
18+
IncludeCategories:
19+
- Regex: '^[<"](Python|structmember|pymem)\.h'
20+
Priority: -3
21+
CaseSensitive: true
22+
- Regex: '^"numpy/'
23+
Priority: -2
24+
- Regex: '^"(npy_pycompat|npy_config)'
25+
Priority: -1
26+
- Regex: '^"[[:alnum:]_.]+"'
27+
Priority: 1
28+
- Regex: '^<[[:alnum:]_.]+"'
29+
Priority: 2
30+
Language: Cpp
31+
PointerAlignment: Right
32+
ReflowComments: true
33+
SpaceBeforeParens: ControlStatements
34+
SpacesInParentheses: false
35+
StatementMacros: [PyObject_HEAD, PyObject_VAR_HEAD, PyObject_HEAD_EXTRA]
36+
TabWidth: 4
37+
UseTab: Never

asciidtype/.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
per-file-ignores = __init__.py:F401

asciidtype/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
.mesonpy*.ini
3+
__pycache__

asciidtype/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# A dtype that stores ASCII data
2+
3+
This is a simple proof-of-concept dtype using the (as of late 2022) experimental
4+
[new dtype
5+
implementation](https://numpy.org/neps/nep-0041-improved-dtype-support.html) in
6+
NumPy.
7+
8+
## Building
9+
10+
Ensure Meson and NumPy are installed in the python environment you would like to use:
11+
12+
```
13+
$ python3 -m pip install meson meson-python numpy build patchelf
14+
```
15+
16+
Build with meson, create a wheel, and install it
17+
18+
```
19+
$ rm -r dist/
20+
$ meson build
21+
$ python -m build --wheel -Cbuilddir=build
22+
$ python -m pip install dist/asciidtype*.whl
23+
```
24+
25+
The `mesonpy` build backend for pip [does not currently support editable
26+
installs](https://github.com/mesonbuild/meson-python/issues/47), so `pip install
27+
-e .` will not work.

asciidtype/asciidtype/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
"""A dtype for working with ASCII data
2+
3+
This is an example usage of the experimental new dtype API
4+
in Numpy and is not intended for any real purpose.
5+
"""
6+
7+
from .scalar import ASCIIScalar # isort: skip
8+
from ._asciidtype_main import ASCIIDType
9+
10+
__all__ = ["ASCIIDType", "ASCIIScalar"]

asciidtype/asciidtype/scalar.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""A scalar type needed by the dtype machinery."""
2+
3+
4+
class ASCIIScalar:
5+
def __init__(self, value, dtype):
6+
self.value = value
7+
self.dtype = dtype
8+
9+
def __str__(self):
10+
return str(self.value)
11+
12+
def __repr__(self):
13+
return repr(self.value)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <Python.h>
2+
3+
#define PY_ARRAY_UNIQUE_SYMBOL asciidtype_ARRAY_API
4+
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
5+
#include "numpy/arrayobject.h"
6+
#include "numpy/experimental_dtype_api.h"
7+
8+
#include "dtype.h"
9+
10+
static struct PyModuleDef moduledef = {
11+
PyModuleDef_HEAD_INIT,
12+
.m_name = "asciidtype_main",
13+
.m_size = -1,
14+
};
15+
16+
/* Module initialization function */
17+
PyMODINIT_FUNC
18+
PyInit__asciidtype_main(void)
19+
{
20+
if (_import_array() < 0) {
21+
return NULL;
22+
}
23+
if (import_experimental_dtype_api(5) < 0) {
24+
return NULL;
25+
}
26+
27+
PyObject *m = PyModule_Create(&moduledef);
28+
if (m == NULL) {
29+
return NULL;
30+
}
31+
32+
PyObject *mod = PyImport_ImportModule("asciidtype");
33+
if (mod == NULL) {
34+
goto error;
35+
}
36+
ASCIIScalar_Type =
37+
(PyTypeObject *)PyObject_GetAttrString(mod, "ASCIIScalar");
38+
Py_DECREF(mod);
39+
40+
if (ASCIIScalar_Type == NULL) {
41+
goto error;
42+
}
43+
44+
if (init_ascii_dtype() < 0) {
45+
goto error;
46+
}
47+
48+
if (PyModule_AddObject(m, "ASCIIDType", (PyObject *)&ASCIIDType) < 0) {
49+
goto error;
50+
}
51+
52+
return m;
53+
54+
error:
55+
Py_DECREF(m);
56+
return NULL;
57+
}

asciidtype/asciidtype/src/casts.c

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#include <Python.h>
2+
3+
#define PY_ARRAY_UNIQUE_SYMBOL asciidtype_ARRAY_API
4+
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
5+
#define NO_IMPORT_ARRAY
6+
#include "numpy/arrayobject.h"
7+
#include "numpy/experimental_dtype_api.h"
8+
#include "numpy/ndarraytypes.h"
9+
10+
#include "casts.h"
11+
#include "dtype.h"
12+
13+
static NPY_CASTING
14+
ascii_to_ascii_resolve_descriptors(PyObject *NPY_UNUSED(self),
15+
PyArray_DTypeMeta *NPY_UNUSED(dtypes[2]),
16+
PyArray_Descr *given_descrs[2],
17+
PyArray_Descr *loop_descrs[2],
18+
npy_intp *view_offset)
19+
{
20+
Py_INCREF(given_descrs[0]);
21+
loop_descrs[0] = given_descrs[0];
22+
if (given_descrs[1] == NULL) {
23+
Py_INCREF(given_descrs[0]);
24+
loop_descrs[1] = given_descrs[0];
25+
}
26+
else {
27+
Py_INCREF(given_descrs[1]);
28+
loop_descrs[1] = given_descrs[0];
29+
}
30+
31+
if (((ASCIIDTypeObject *)loop_descrs[0])->size ==
32+
((ASCIIDTypeObject *)loop_descrs[1])->size) {
33+
*view_offset = 0;
34+
return NPY_NO_CASTING;
35+
}
36+
37+
return NPY_SAME_KIND_CASTING;
38+
}
39+
40+
static int
41+
ascii_to_ascii_contiguous(PyArrayMethod_Context *context, char *const data[],
42+
npy_intp const dimensions[],
43+
npy_intp const NPY_UNUSED(strides[]),
44+
NpyAuxData *NPY_UNUSED(auxdata))
45+
{
46+
PyArray_Descr **descrs = context->descriptors;
47+
// for contiguous assignment the sizes of the two dtypes should be
48+
// the same, consider adding an assert to check?
49+
long size = ((ASCIIDTypeObject *)descrs[0])->size;
50+
51+
npy_intp N = dimensions[0] * size;
52+
char *in = data[0];
53+
char *out = data[1];
54+
55+
while (N--) {
56+
*out = *in;
57+
out++;
58+
in++;
59+
}
60+
61+
return 0;
62+
}
63+
64+
static int
65+
ascii_to_ascii_strided_or_unaligned(PyArrayMethod_Context *context,
66+
char *const data[],
67+
npy_intp const dimensions[],
68+
npy_intp const strides[],
69+
NpyAuxData *NPY_UNUSED(auxdata))
70+
{
71+
PyArray_Descr **descrs = context->descriptors;
72+
long in_size = ((ASCIIDTypeObject *)descrs[0])->size;
73+
long out_size = ((ASCIIDTypeObject *)descrs[1])->size;
74+
long copy_size;
75+
76+
if (out_size > in_size) {
77+
copy_size = in_size;
78+
}
79+
else {
80+
copy_size = out_size;
81+
}
82+
83+
npy_intp N = dimensions[0];
84+
char *in = data[0];
85+
char *out = data[1];
86+
npy_intp in_stride = strides[0];
87+
npy_intp out_stride = strides[1];
88+
89+
while (N--) {
90+
memcpy(out, in, out_size * sizeof(char)); // NOLINT
91+
for (int i = copy_size; i < out_size; i++) {
92+
*(out + i) = '\0';
93+
}
94+
in += in_stride;
95+
out += out_stride;
96+
}
97+
98+
return 0;
99+
}
100+
101+
static int
102+
ascii_to_ascii_get_loop(PyArrayMethod_Context *context, int aligned,
103+
int NPY_UNUSED(move_references),
104+
const npy_intp *strides,
105+
PyArrayMethod_StridedLoop **out_loop,
106+
NpyAuxData **NPY_UNUSED(out_transferdata),
107+
NPY_ARRAYMETHOD_FLAGS *flags)
108+
{
109+
PyArray_Descr **descrs = context->descriptors;
110+
111+
int contig = (strides[0] == ((ASCIIDTypeObject *)descrs[0])->size *
112+
sizeof(char) &&
113+
strides[1] == ((ASCIIDTypeObject *)descrs[1])->size *
114+
sizeof(char));
115+
116+
if (aligned && contig) {
117+
*out_loop = (PyArrayMethod_StridedLoop *)&ascii_to_ascii_contiguous;
118+
}
119+
else {
120+
*out_loop = (PyArrayMethod_StridedLoop
121+
*)&ascii_to_ascii_strided_or_unaligned;
122+
}
123+
124+
*flags = 0;
125+
return 0;
126+
}
127+
128+
static PyArray_DTypeMeta *a2a_dtypes[2] = {NULL, NULL};
129+
130+
static PyType_Slot a2a_slots[] = {
131+
{NPY_METH_resolve_descriptors, &ascii_to_ascii_resolve_descriptors},
132+
{_NPY_METH_get_loop, &ascii_to_ascii_get_loop},
133+
{0, NULL}};
134+
135+
PyArrayMethod_Spec ASCIIToASCIICastSpec = {
136+
.name = "cast_ASCIIDType_to_ASCIIDType",
137+
.nin = 1,
138+
.nout = 1,
139+
.flags = NPY_METH_SUPPORTS_UNALIGNED,
140+
.casting = NPY_SAME_KIND_CASTING,
141+
.dtypes = a2a_dtypes,
142+
.slots = a2a_slots,
143+
};

0 commit comments

Comments
 (0)