├── .gitmodules ├── asv_benchmarks ├── .gitignore └── benchmarks │ ├── __init__.py │ └── strings.py ├── quaddtype ├── numpy_quaddtype │ ├── py.typed │ ├── src │ │ ├── umath │ │ │ ├── binary_ops.h │ │ │ ├── comparison_ops.h │ │ │ ├── unary_ops.h │ │ │ ├── unary_props.h │ │ │ ├── umath.h │ │ │ ├── matmul.h │ │ │ ├── promoters.hpp │ │ │ ├── umath.cpp │ │ │ └── unary_props.cpp │ │ ├── casts.h │ │ ├── lock.c │ │ ├── scalar_ops.h │ │ ├── lock.h │ │ ├── dtype.h │ │ ├── scalar.h │ │ ├── quad_common.h │ │ ├── quadblas_interface.h │ │ ├── dragon4.h │ │ ├── utilities.h │ │ ├── quaddtype_main.c │ │ ├── utilities.c │ │ ├── constants.hpp │ │ └── quadblas_interface.cpp │ ├── __init__.pyi │ ├── __init__.py │ └── _quaddtype_main.pyi ├── subprojects │ ├── sleef.wrap │ ├── qblas.wrap │ └── packagefiles │ │ ├── qblas │ │ └── meson.build │ │ └── sleef │ │ └── meson.build ├── sdist_test.sh ├── reinstall.sh ├── tests │ ├── test_multithreading.py │ ├── utils.py │ └── test_finfo.py ├── LICENSE ├── pyproject.toml ├── README.md └── meson.build ├── mpfdtype ├── .gitignore ├── mpfdtype │ ├── tests │ │ ├── conftest.py │ │ ├── test_array.py │ │ └── test_scalar.py │ ├── src │ │ ├── terrible_hacks.h │ │ ├── umath.h │ │ ├── casts.h │ │ ├── numbers.h │ │ ├── scalar.h │ │ ├── mpfdtype_main.c │ │ ├── dtype.h │ │ ├── terrible_hacks.c │ │ ├── ops.hpp │ │ ├── numbers.cpp │ │ └── scalar.c │ └── __init__.py ├── reinstall.sh ├── pyproject.toml ├── meson.build └── README.md ├── asciidtype ├── .gitignore ├── .flake8 ├── tests │ └── conftest.py ├── asciidtype │ ├── src │ │ ├── umath.h │ │ ├── casts.h │ │ ├── dtype.h │ │ ├── asciidtype_main.c │ │ └── umath.c │ ├── __init__.py │ └── scalar.py ├── reinstall.sh ├── pyproject.toml ├── README.md ├── meson.build └── .clang-format ├── metadatadtype ├── .gitignore ├── .flake8 ├── tests │ ├── conftest.py │ └── test_metadatadtype.py ├── metadatadtype │ ├── src │ │ ├── umath.h │ │ ├── casts.h │ │ ├── dtype.h │ │ ├── metadatadtype_main.c │ │ └── umath.c │ ├── scalar.py │ └── __init__.py ├── reinstall.sh ├── pyproject.toml ├── README.md ├── meson.build └── .clang-format ├── stringdtype ├── .gitignore ├── tests │ └── conftest.py ├── stringdtype │ ├── scalar.py │ ├── src │ │ ├── umath.h │ │ ├── casts.h │ │ ├── main.c │ │ ├── dtype.h │ │ └── static_string.h │ └── __init__.py ├── reinstall.sh ├── pyproject.toml ├── .clang-format ├── README.md └── meson.build ├── unytdtype ├── .gitignore ├── tests │ ├── conftest.py │ └── test_unytdtype.py ├── unytdtype │ ├── src │ │ ├── umath.h │ │ ├── dtype.h │ │ ├── casts.h │ │ ├── unytdtype_main.c │ │ └── umath.c │ ├── __init__.py │ └── scalar.py ├── pyproject.toml ├── reinstall.sh ├── meson.build ├── README.md └── .clang-format ├── .github ├── dependabot.yml └── workflows │ ├── typecheck.yml │ ├── ci.yml │ └── big_endian.yml ├── README.md ├── .clang-format ├── LICENSE ├── .gitignore └── .pre-commit-config.yaml /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /asv_benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | strings 2 | -------------------------------------------------------------------------------- /asv_benchmarks/benchmarks/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mpfdtype/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .mesonpy*.ini 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /asciidtype/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .mesonpy*.ini 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /metadatadtype/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .mesonpy*.ini 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /stringdtype/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .mesonpy*.ini 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /unytdtype/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | .mesonpy*.ini 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /metadatadtype/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | per-file-ignores = __init__.py:F401 3 | -------------------------------------------------------------------------------- /asciidtype/.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | per-file-ignores = __init__.py:F401 3 | max-line-length = 160 4 | -------------------------------------------------------------------------------- /asciidtype/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.environ["NUMPY_EXPERIMENTAL_DTYPE_API"] = "1" 4 | -------------------------------------------------------------------------------- /metadatadtype/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.environ["NUMPY_EXPERIMENTAL_DTYPE_API"] = "1" 4 | -------------------------------------------------------------------------------- /stringdtype/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.environ["NUMPY_EXPERIMENTAL_DTYPE_API"] = "1" 4 | -------------------------------------------------------------------------------- /unytdtype/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.environ["NUMPY_EXPERIMENTAL_DTYPE_API"] = "1" 4 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | os.environ["NUMPY_EXPERIMENTAL_DTYPE_API"] = "1" 4 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/terrible_hacks.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | int 5 | init_terrible_hacks(void); 6 | -------------------------------------------------------------------------------- /stringdtype/stringdtype/scalar.py: -------------------------------------------------------------------------------- 1 | """Scalar types needed by the dtype machinery.""" 2 | 3 | 4 | class StringScalar(str): 5 | pass 6 | -------------------------------------------------------------------------------- /asciidtype/asciidtype/src/umath.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_UFUNC_H 2 | #define _NPY_UFUNC_H 3 | 4 | int 5 | init_ufuncs(void); 6 | 7 | #endif /*_NPY_UFUNC_H */ 8 | -------------------------------------------------------------------------------- /stringdtype/stringdtype/src/umath.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_UFUNC_H 2 | #define _NPY_UFUNC_H 3 | 4 | int 5 | init_ufuncs(void); 6 | 7 | #endif /*_NPY_UFUNC_H */ 8 | -------------------------------------------------------------------------------- /metadatadtype/metadatadtype/src/umath.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_UFUNC_H 2 | #define _NPY_UFUNC_H 3 | 4 | int 5 | init_ufuncs(void); 6 | 7 | #endif /*_NPY_UFUNC_H */ 8 | -------------------------------------------------------------------------------- /unytdtype/unytdtype/src/umath.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_UFUNC_H 2 | #define _NPY_UFUNC_H 3 | 4 | int 5 | init_multiply_ufunc(void); 6 | 7 | #endif /*_NPY_UFUNC_H */ 8 | -------------------------------------------------------------------------------- /stringdtype/stringdtype/src/casts.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_CASTS_H 2 | #define _NPY_CASTS_H 3 | 4 | PyArrayMethod_Spec ** 5 | get_casts(); 6 | 7 | #endif /* _NPY_CASTS_H */ 8 | -------------------------------------------------------------------------------- /asciidtype/asciidtype/src/casts.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_CASTS_H 2 | #define _NPY_CASTS_H 3 | 4 | PyArrayMethod_Spec ** 5 | get_casts(void); 6 | 7 | #endif /* _NPY_CASTS_H */ 8 | -------------------------------------------------------------------------------- /metadatadtype/metadatadtype/src/casts.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_CASTS_H 2 | #define _NPY_CASTS_H 3 | 4 | PyArrayMethod_Spec ** 5 | get_casts(void); 6 | 7 | #endif /* _NPY_CASTS_H */ 8 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/binary_ops.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_BINARY_OPS_H 2 | #define _QUADDTYPE_BINARY_OPS_H 3 | 4 | #include 5 | 6 | int 7 | init_quad_binary_ops(PyObject *numpy); 8 | 9 | #endif -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/comparison_ops.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_COMPARISON_OPS_H 2 | #define _QUADDTYPE_COMPARISON_OPS_H 3 | 4 | #include 5 | 6 | int 7 | init_quad_comps(PyObject *numpy); 8 | 9 | #endif -------------------------------------------------------------------------------- /quaddtype/subprojects/sleef.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=sleef 3 | url=https://github.com/shibatch/sleef.git 4 | revision=3.8 5 | patch_directory=sleef 6 | 7 | [provide] 8 | sleef = sleef_dep 9 | sleefquad = sleefquad_dep -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/unary_ops.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_UNARY_OPS_H 2 | #define _QUADDTYPE_UNARY_OPS_H 3 | 4 | #include 5 | 6 | int 7 | init_quad_unary_ops(PyObject *numpy); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/unary_props.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_UNARY_PROPS_H 2 | #define _QUADDTYPE_UNARY_PROPS_H 3 | 4 | #include 5 | 6 | int 7 | init_quad_unary_props(PyObject *numpy); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /quaddtype/subprojects/qblas.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=qblas 3 | url=https://github.com/SwayamInSync/QBLAS.git 4 | revision=9468e24a02b731563eba2aee0350e9219b36c102 5 | patch_directory = qblas 6 | 7 | [provide] 8 | qblas = qblas_dep 9 | -------------------------------------------------------------------------------- /quaddtype/sdist_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | if [ -d "build/" ]; then 5 | rm -rf dist/ 6 | fi 7 | 8 | python -m pip uninstall -y numpy_quaddtype 9 | python -m build --sdist --outdir dist/ 10 | python -m pip install dist/*.tar.gz -v -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/umath.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_UMATH_H 2 | #define _QUADDTYPE_UMATH_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int 9 | init_quad_umath(void); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | 15 | #endif -------------------------------------------------------------------------------- /quaddtype/subprojects/packagefiles/qblas/meson.build: -------------------------------------------------------------------------------- 1 | project('qblas', version: '1.0.0') 2 | 3 | qblas_inc = include_directories('include') 4 | 5 | qblas_dep = declare_dependency( 6 | include_directories: qblas_inc, 7 | version: meson.project_version() 8 | ) 9 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/umath.h: -------------------------------------------------------------------------------- 1 | #ifndef _MPRFDTYPE_UMATH_H 2 | #define _MPRFDTYPE_UMATH_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | int 9 | init_mpf_umath(void); 10 | 11 | #ifdef __cplusplus 12 | } 13 | #endif 14 | 15 | #endif /*_MPRFDTYPE_UMATH_H */ 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | labels: [dependabot] 8 | commit-message: 9 | prefix: "🤖" 10 | groups: 11 | actions: 12 | patterns: ["*"] 13 | -------------------------------------------------------------------------------- /metadatadtype/metadatadtype/scalar.py: -------------------------------------------------------------------------------- 1 | """A scalar type needed by the dtype machinery.""" 2 | 3 | 4 | class MetadataScalar: 5 | def __init__(self, value, dtype): 6 | self.value = value 7 | self.dtype = dtype 8 | 9 | def __repr__(self): 10 | return f"{self.value} {self.dtype._metadata}" 11 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/matmul.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_MATMUL_H 2 | #define _QUADDTYPE_MATMUL_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | int 11 | init_matmul_ops(PyObject *numpy); 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif // _QUADDTYPE_MATMUL_H -------------------------------------------------------------------------------- /stringdtype/stringdtype/__init__.py: -------------------------------------------------------------------------------- 1 | """A dtype for working with variable-length string data 2 | 3 | """ 4 | 5 | from .scalar import StringScalar # isort: skip 6 | from ._main import StringDType, _memory_usage 7 | 8 | __all__ = [ 9 | "NA", 10 | "StringDType", 11 | "StringScalar", 12 | "_memory_usage", 13 | ] 14 | -------------------------------------------------------------------------------- /unytdtype/unytdtype/__init__.py: -------------------------------------------------------------------------------- 1 | """A dtype that carries around unit metadata. 2 | 3 | This is an example usage of the experimental new dtype API 4 | in Numpy and is not yet intended for any real purpose. 5 | """ 6 | 7 | from .scalar import UnytScalar # isort: skip 8 | from ._unytdtype_main import UnytDType 9 | 10 | __all__ = ["UnytDType", "UnytScalar"] 11 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/casts.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_CASTS_H 2 | #define _NPY_CASTS_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | extern PyArrayMethod_Spec MPFToMPFCastSpec; 9 | 10 | PyArrayMethod_Spec ** 11 | init_casts(void); 12 | 13 | void 14 | free_casts(void); 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | #endif /* _NPY_CASTS_H */ 21 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/numbers.h: -------------------------------------------------------------------------------- 1 | #ifndef _MPF_NUMBERS_H 2 | #define _MPF_NUMBERS_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | PyObject * 9 | mpf_richcompare(MPFloatObject *self, PyObject *other, int cmp_op); 10 | 11 | extern PyNumberMethods mpf_as_number; 12 | 13 | #ifdef __cplusplus 14 | } 15 | #endif 16 | 17 | #endif /* _MPF_NUMBERS_H */ 18 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/casts.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_CASTS_H 2 | #define _QUADDTYPE_CASTS_H 3 | 4 | #include 5 | #include "numpy/dtype_api.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | PyArrayMethod_Spec ** 12 | init_casts(void); 13 | 14 | void 15 | free_casts(void); 16 | 17 | #ifdef __cplusplus 18 | } 19 | #endif 20 | 21 | #endif -------------------------------------------------------------------------------- /metadatadtype/metadatadtype/__init__.py: -------------------------------------------------------------------------------- 1 | """A dtype that carries around metadata. 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 MetadataScalar # isort: skip 8 | from ._metadatadtype_main import MetadataDType 9 | 10 | __all__ = ["MetadataDType", "MetadataScalar"] 11 | -------------------------------------------------------------------------------- /asciidtype/asciidtype/__init__.py: -------------------------------------------------------------------------------- 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__ = [ 11 | "ASCIIDType", 12 | "ASCIIScalar", 13 | ] 14 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/lock.c: -------------------------------------------------------------------------------- 1 | #include "lock.h" 2 | 3 | #if PY_VERSION_HEX < 0x30d00b3 4 | PyThread_type_lock sleef_lock = NULL; 5 | #else 6 | PyMutex sleef_lock = {0}; 7 | #endif 8 | 9 | void init_sleef_locks(void) 10 | { 11 | #if PY_VERSION_HEX < 0x30d00b3 12 | sleef_lock = PyThread_allocate_lock(); 13 | if (!sleef_lock) { 14 | PyErr_NoMemory(); 15 | } 16 | #endif 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # numpy-user-dtypes 2 | 3 | Repository for development of dtypes making use of the [NEP 4 | 42](https://numpy.org/neps/nep-0042-new-dtypes.html) extensible dtype API. See 5 | the readme files in each example dtype for build instructions. 6 | 7 | These dtypes are not meant for real-world use yet. The dtype API is not 8 | finalized and the dtypes in this repository are still active works in progress. 9 | -------------------------------------------------------------------------------- /asciidtype/asciidtype/src/dtype.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_DTYPE_H 2 | #define _NPY_DTYPE_H 3 | 4 | typedef struct { 5 | PyArray_Descr base; 6 | long size; 7 | } ASCIIDTypeObject; 8 | 9 | extern PyArray_DTypeMeta ASCIIDType; 10 | extern PyTypeObject *ASCIIScalar_Type; 11 | 12 | ASCIIDTypeObject * 13 | new_asciidtype_instance(long size); 14 | 15 | int 16 | init_ascii_dtype(void); 17 | 18 | #endif /*_NPY_DTYPE_H*/ 19 | -------------------------------------------------------------------------------- /unytdtype/unytdtype/src/dtype.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_DTYPE_H 2 | #define _NPY_DTYPE_H 3 | 4 | typedef struct { 5 | PyArray_Descr base; 6 | PyObject *unit; 7 | } UnytDTypeObject; 8 | 9 | extern PyArray_DTypeMeta UnytDType; 10 | extern PyTypeObject *UnytScalar_Type; 11 | 12 | UnytDTypeObject * 13 | new_unytdtype_instance(PyObject *unit); 14 | 15 | int 16 | init_unyt_dtype(void); 17 | 18 | #endif /*_NPY_DTYPE_H*/ 19 | -------------------------------------------------------------------------------- /unytdtype/unytdtype/src/casts.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_CASTS_H 2 | #define _NPY_CASTS_H 3 | 4 | /* Gets the conversion between two units: */ 5 | int 6 | get_conversion_factor(PyObject *from_unit, PyObject *to_unit, double *factor, 7 | double *offset); 8 | 9 | PyArrayMethod_Spec ** 10 | get_casts(void); 11 | 12 | int 13 | UnitConverter(PyObject *obj, PyObject **unit); 14 | 15 | #endif /* _NPY_CASTS_H */ 16 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/scalar_ops.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_SCALAR_OPS_H 2 | #define _QUADDTYPE_SCALAR_OPS_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include "dtype.h" 10 | 11 | PyObject * 12 | quad_richcompare(QuadPrecisionObject *self, PyObject *other, int cmp_op); 13 | 14 | extern PyNumberMethods quad_as_scalar; 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /unytdtype/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "meson>=0.63.0", 4 | "meson-python", 5 | "patchelf", 6 | "wheel", 7 | "numpy", 8 | ] 9 | build-backend = "mesonpy" 10 | 11 | [project] 12 | name = "unytdtype" 13 | description = "A unit dtype backed by unyt" 14 | version = "0.0.1" 15 | readme = 'README.md' 16 | author = "Nathan Goldbaum" 17 | requires-python = ">=3.9.0" 18 | dependencies = [ 19 | "numpy" 20 | ] 21 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/__init__.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from ._mpfdtype_main import MPFDType, MPFloat 4 | 5 | 6 | 7 | # Lets add some uglier hacks: 8 | 9 | # NumPy uses repr as a fallback (as of writing this code), we want to 10 | # customize the printing of MPFloats though... 11 | def mystr(obj): 12 | if isinstance(obj, MPFloat): 13 | return f"'{obj}'" 14 | return repr(obj) 15 | 16 | np.set_printoptions(formatter={"numpystr": mystr}) 17 | -------------------------------------------------------------------------------- /mpfdtype/reinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeuo pipefail 3 | IFS=$'\n\t' 4 | 5 | if [ -d "build/" ] 6 | then 7 | rm -r build 8 | fi 9 | 10 | #meson setup build -Db_sanitize=address,undefined 11 | meson setup build 12 | python -m pip uninstall -y mpfdtype 13 | python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' -Csetup-args="-Dbuildtype=debug" 14 | #python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' 15 | -------------------------------------------------------------------------------- /asciidtype/reinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeuo pipefail 3 | IFS=$'\n\t' 4 | 5 | if [ -d "build/" ] 6 | then 7 | rm -r build 8 | fi 9 | 10 | #meson setup build -Db_sanitize=address,undefined 11 | meson setup build 12 | python -m pip uninstall -y asciidtype 13 | python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' -Csetup-args="-Dbuildtype=debug" 14 | #python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' 15 | -------------------------------------------------------------------------------- /unytdtype/reinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeuo pipefail 3 | IFS=$'\n\t' 4 | 5 | if [ -d "build/" ] 6 | then 7 | rm -r build 8 | fi 9 | 10 | #meson setup build -Db_sanitize=address,undefined 11 | meson setup build 12 | python -m pip uninstall -y unytdtype 13 | python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' -Csetup-args="-Dbuildtype=debug" 14 | #python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' 15 | -------------------------------------------------------------------------------- /metadatadtype/reinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeuo pipefail 3 | IFS=$'\n\t' 4 | 5 | if [ -d "build/" ] 6 | then 7 | rm -r build 8 | fi 9 | 10 | #meson setup build -Db_sanitize=address,undefined 11 | meson setup build 12 | python -m pip uninstall -y metadatadtype 13 | python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' -Csetup-args="-Dbuildtype=debug" 14 | #python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' 15 | -------------------------------------------------------------------------------- /mpfdtype/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "meson>=0.63.0", 4 | "meson-python", 5 | "patchelf", 6 | "wheel", 7 | "numpy", 8 | ] 9 | build-backend = "mesonpy" 10 | 11 | [project] 12 | name = "mpfdtype" 13 | description = "A dtype backing MPFR multi-precision floats" 14 | version = "0.0.1" 15 | readme = 'README.md' 16 | author = "Sebastian Berg and NumPy Developers" 17 | requires-python = ">=3.9.0" 18 | dependencies = [ 19 | "numpy" 20 | ] 21 | -------------------------------------------------------------------------------- /stringdtype/reinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -xeuo pipefail 3 | IFS=$'\n\t' 4 | 5 | if [ -d "build/" ] 6 | then 7 | rm -r build 8 | fi 9 | 10 | #meson setup build -Db_sanitize=address,undefined 11 | meson setup build 12 | python -m pip uninstall -y stringdtype 13 | python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' -Csetup-args="-Dbuildtype=debug" 14 | #python -m pip install . -v --no-build-isolation -Cbuilddir=build -C'compile-args=-v' 15 | -------------------------------------------------------------------------------- /asciidtype/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "meson>=0.63.0", 4 | "meson-python", 5 | "patchelf", 6 | "wheel", 7 | "numpy", 8 | ] 9 | build-backend = "mesonpy" 10 | 11 | [tool.black] 12 | line-length = 79 13 | 14 | [project] 15 | name = "asciidtype" 16 | description = "A dtype for ASCII data" 17 | version = "0.0.1" 18 | readme = 'README.md' 19 | author = "Nathan Goldbaum" 20 | requires-python = ">=3.9.0" 21 | dependencies = [ 22 | "numpy", 23 | ] 24 | -------------------------------------------------------------------------------- /asciidtype/asciidtype/scalar.py: -------------------------------------------------------------------------------- 1 | """A scalar type needed by the dtype machinery.""" 2 | 3 | 4 | class ASCIIScalar(str): 5 | def __new__(cls, value, dtype): 6 | instance = super().__new__(cls, value) 7 | instance.dtype = dtype 8 | return instance 9 | 10 | def partition(self, sep): 11 | ret = super().partition(sep) 12 | return (str(ret[0]), str(ret[1]), str(ret[2])) 13 | 14 | def rpartition(self, sep): 15 | ret = super().rpartition(sep) 16 | return (str(ret[0]), str(ret[1]), str(ret[2])) 17 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/lock.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_LOCK_H 2 | #define _QUADDTYPE_LOCK_H 3 | 4 | #include 5 | 6 | #if PY_VERSION_HEX < 0x30d00b3 7 | extern PyThread_type_lock sleef_lock; 8 | #define LOCK_SLEEF PyThread_acquire_lock(sleef_lock, WAIT_LOCK) 9 | #define UNLOCK_SLEEF PyThread_release_lock(sleef_lock) 10 | #else 11 | extern PyMutex sleef_lock; 12 | #define LOCK_SLEEF PyMutex_Lock(&sleef_lock) 13 | #define UNLOCK_SLEEF PyMutex_Unlock(&sleef_lock) 14 | #endif 15 | 16 | void init_sleef_locks(void); 17 | 18 | #endif // _QUADDTYPE_LOCK_H -------------------------------------------------------------------------------- /metadatadtype/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "meson>=0.63.0", 4 | "meson-python", 5 | "patchelf", 6 | "wheel", 7 | "numpy", 8 | ] 9 | build-backend = "mesonpy" 10 | 11 | [project] 12 | name = "metadatadtype" 13 | description = "A dtype that holds a piece of metadata" 14 | version = "0.0.1" 15 | readme = 'README.md' 16 | author = "Nathan Goldbaum" 17 | requires-python = ">=3.9.0" 18 | dependencies = [ 19 | "numpy", 20 | ] 21 | 22 | [tool.meson-python.args] 23 | dist = [] 24 | setup = ["-Ddebug=true", "-Doptimization=0"] 25 | compile = [] 26 | install = [] 27 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/tests/test_array.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_equal 3 | 4 | from mpfdtype import MPFDType 5 | 6 | 7 | def test_advanced_indexing(): 8 | # As of writing the test, this relies on copyswap 9 | arr = np.arange(100).astype(MPFDType(100)) 10 | orig = np.arange(100).astype(MPFDType(100)) # second one, not a copy 11 | 12 | b = arr[[1, 2, 3, 4]] 13 | b[...] = 5 # does not mutate arr (internal references not broken) 14 | assert_array_equal(arr, orig) 15 | 16 | 17 | def test_is_numeric(): 18 | assert MPFDType._is_numeric 19 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/dtype.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_DTYPE_H 2 | #define _QUADDTYPE_DTYPE_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include "quad_common.h" 12 | 13 | typedef struct { 14 | PyArray_Descr base; 15 | QuadBackendType backend; 16 | } QuadPrecDTypeObject; 17 | 18 | extern PyArray_DTypeMeta QuadPrecDType; 19 | 20 | QuadPrecDTypeObject * 21 | new_quaddtype_instance(QuadBackendType backend); 22 | 23 | int 24 | init_quadprec_dtype(void); 25 | 26 | #ifdef __cplusplus 27 | } 28 | #endif 29 | 30 | #endif -------------------------------------------------------------------------------- /quaddtype/reinstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | if [ -d "build/" ]; then 5 | rm -r build 6 | rm -rf dist/ 7 | rm -rf subprojects/qblas 8 | rm -rf subprojects/sleef 9 | fi 10 | 11 | python -m pip uninstall -y numpy_quaddtype 12 | python -m pip install . -vv --no-build-isolation 2>&1 | tee build_log.txt 13 | 14 | # for debugging and TSAN builds, comment the above line and uncomment all below: 15 | # export CFLAGS="-fsanitize=thread -g -O0" 16 | # export CXXFLAGS="-fsanitize=thread -g -O0" 17 | # export LDFLAGS="-fsanitize=thread" 18 | # CC=clang CXX=clang++ python -m pip install . -vv --no-build-isolation -Csetup-args=-Db_sanitize=thread 2>&1 | tee build_log.txt -------------------------------------------------------------------------------- /metadatadtype/metadatadtype/src/dtype.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_DTYPE_H 2 | #define _NPY_DTYPE_H 3 | 4 | typedef struct { 5 | PyArray_Descr base; 6 | PyObject *metadata; 7 | } MetadataDTypeObject; 8 | 9 | extern PyArray_DTypeMeta MetadataDType; 10 | extern PyTypeObject *MetadataScalar_Type; 11 | 12 | MetadataDTypeObject * 13 | new_metadatadtype_instance(PyObject *metadata); 14 | 15 | int 16 | init_metadata_dtype(void); 17 | 18 | PyArray_Descr * 19 | common_instance(MetadataDTypeObject *dtype1, 20 | MetadataDTypeObject *NPY_UNUSED(dtype2)); 21 | 22 | // from numpy's dtypemeta.h, not publicly available 23 | #define NPY_DTYPE(descr) ((PyArray_DTypeMeta *)Py_TYPE(descr)) 24 | 25 | #endif /*_NPY_DTYPE_H*/ 26 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/scalar.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_SCALAR_H 2 | #define _QUADDTYPE_SCALAR_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include "quad_common.h" 11 | 12 | typedef struct { 13 | PyObject_HEAD 14 | quad_value value; 15 | QuadBackendType backend; 16 | } QuadPrecisionObject; 17 | 18 | extern PyTypeObject QuadPrecision_Type; 19 | 20 | QuadPrecisionObject * 21 | QuadPrecision_raw_new(QuadBackendType backend); 22 | 23 | QuadPrecisionObject * 24 | QuadPrecision_from_object(PyObject *value, QuadBackendType backend); 25 | 26 | int 27 | init_quadprecision_scalar(void); 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/scalar.h: -------------------------------------------------------------------------------- 1 | #ifndef _MPRFDTYPE_SCALAR_H 2 | #define _MPRFDTYPE_SCALAR_H 3 | 4 | #include "mpfr.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #include 11 | 12 | 13 | typedef struct { 14 | mpfr_t x; 15 | mp_limb_t significand[]; 16 | } mpf_field; 17 | 18 | 19 | typedef struct { 20 | PyObject_VAR_HEAD; 21 | mpf_field mpf; 22 | } MPFloatObject; 23 | 24 | 25 | extern PyTypeObject MPFloat_Type; 26 | 27 | MPFloatObject * 28 | MPFLoat_raw_new(mpfr_prec_t prec); 29 | 30 | mpfr_prec_t 31 | get_prec_from_object(PyObject *value); 32 | 33 | MPFloatObject * 34 | MPFloat_from_object(PyObject *value, Py_ssize_t prec); 35 | 36 | int 37 | init_mpf_scalar(void); 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif /* _MPRFDTYPE_SCALAR_H */ -------------------------------------------------------------------------------- /stringdtype/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "meson>=0.63.0", 4 | "meson-python", 5 | "patchelf", 6 | "wheel", 7 | "numpy", 8 | ] 9 | build-backend = "mesonpy" 10 | 11 | [tool.black] 12 | line-length = 79 13 | 14 | [tool.isort] 15 | profile = "black" 16 | line_length = 79 17 | 18 | [project] 19 | name = "stringdtype" 20 | description = "A dtype for storing UTF-8 strings" 21 | version = "0.0.1" 22 | readme = 'README.md' 23 | authors = [ 24 | { name = "Nathan Goldbaum" }, 25 | { name = "Peyton Murray" } 26 | ] 27 | requires-python = ">=3.9.0" 28 | dependencies = [ 29 | "numpy", 30 | ] 31 | 32 | [tool.ruff] 33 | line-length = 79 34 | per-file-ignores = {"__init__.py" = ["F401"]} 35 | 36 | [tool.meson-python.args] 37 | dist = [] 38 | setup = [] 39 | compile = [] 40 | install = [] 41 | -------------------------------------------------------------------------------- /asciidtype/README.md: -------------------------------------------------------------------------------- 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. 28 | -------------------------------------------------------------------------------- /unytdtype/unytdtype/scalar.py: -------------------------------------------------------------------------------- 1 | """A scalar type needed by the dtype machinery.""" 2 | 3 | from unyt import Unit 4 | 5 | 6 | class UnytScalar: 7 | def __init__(self, value, unit): 8 | from . import UnytDType 9 | self.value = value 10 | if isinstance(unit, (str, Unit)): 11 | self.dtype = UnytDType(unit) 12 | elif isinstance(unit, UnytDType): 13 | self.dtype = unit 14 | else: 15 | raise RuntimeError 16 | 17 | @property 18 | def unit(self): 19 | return self.dtype.unit 20 | 21 | def __repr__(self): 22 | return f"{self.value} {self.dtype.unit}" 23 | 24 | def __rmul__(self, other): 25 | return UnytScalar(self.value * other, self.dtype.unit) 26 | 27 | def __eq__(self, other): 28 | return self.value == other.value and self.dtype == other.dtype 29 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/quad_common.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_COMMON_H 2 | #define _QUADDTYPE_COMMON_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | typedef enum { 12 | BACKEND_INVALID = -1, 13 | BACKEND_SLEEF, 14 | BACKEND_LONGDOUBLE 15 | } QuadBackendType; 16 | 17 | typedef union { 18 | Sleef_quad sleef_value; 19 | long double longdouble_value; 20 | } quad_value; 21 | 22 | 23 | // For IEEE 754 binary128 (quad precision), we need 36 decimal digits 24 | // to guarantee round-trip conversion (string -> parse -> equals original value) 25 | // Formula: ceil(1 + MANT_DIG * log10(2)) = ceil(1 + 113 * 0.30103) = 36 26 | // src: https://en.wikipedia.org/wiki/Quadruple-precision_floating-point_format 27 | #define SLEEF_QUAD_DECIMAL_DIG 36 28 | 29 | #ifdef __cplusplus 30 | } 31 | #endif 32 | 33 | #endif -------------------------------------------------------------------------------- /metadatadtype/README.md: -------------------------------------------------------------------------------- 1 | # A dtype that stores metadata 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. For now all it does it storea piece of static metadata in the dtype 7 | itself. 8 | 9 | ## Building 10 | 11 | Ensure Meson and NumPy are installed in the python environment you would like to use: 12 | 13 | ``` 14 | $ python3 -m pip install meson meson-python numpy build patchelf 15 | ``` 16 | 17 | Build with meson, create a wheel, and install it 18 | 19 | ``` 20 | $ rm -r dist/ 21 | $ meson build 22 | $ python -m build --wheel -Cbuilddir=build 23 | $ python -m pip install --force-reinstall dist/metadatadtype*.whl 24 | ``` 25 | 26 | The `mesonpy` build backend for pip [does not currently support editable 27 | installs](https://github.com/mesonbuild/meson-python/issues/47), so `pip install 28 | -e .` will not work. 29 | -------------------------------------------------------------------------------- /unytdtype/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'unytdtype', 3 | 'c', 4 | ) 5 | 6 | py_mod = import('python') 7 | py = py_mod.find_installation() 8 | 9 | incdir_numpy = run_command(py, 10 | [ 11 | '-c', 12 | 'import numpy; print(numpy.get_include())' 13 | ], 14 | check: true 15 | ).stdout().strip() 16 | 17 | includes = include_directories( 18 | [ 19 | incdir_numpy, 20 | 'unytdtype/src' 21 | ] 22 | ) 23 | 24 | srcs = [ 25 | 'unytdtype/src/casts.c', 26 | 'unytdtype/src/casts.h', 27 | 'unytdtype/src/dtype.c', 28 | 'unytdtype/src/unytdtype_main.c', 29 | 'unytdtype/src/umath.c', 30 | 'unytdtype/src/umath.h', 31 | ] 32 | 33 | py.install_sources( 34 | [ 35 | 'unytdtype/__init__.py', 36 | 'unytdtype/scalar.py' 37 | ], 38 | subdir: 'unytdtype', 39 | pure: false 40 | ) 41 | 42 | py.extension_module( 43 | '_unytdtype_main', 44 | srcs, 45 | c_args: ['-g', '-O0'], 46 | install: true, 47 | subdir: 'unytdtype', 48 | include_directories: includes 49 | ) 50 | -------------------------------------------------------------------------------- /asciidtype/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'asciidtype', 3 | 'c', 4 | ) 5 | 6 | py_mod = import('python') 7 | py = py_mod.find_installation() 8 | 9 | incdir_numpy = run_command(py, 10 | [ 11 | '-c', 12 | 'import numpy; print(numpy.get_include())' 13 | ], 14 | check: true 15 | ).stdout().strip() 16 | 17 | includes = include_directories( 18 | [ 19 | incdir_numpy, 20 | 'asciidtype/src' 21 | ] 22 | ) 23 | 24 | srcs = [ 25 | 'asciidtype/src/casts.c', 26 | 'asciidtype/src/casts.h', 27 | 'asciidtype/src/dtype.c', 28 | 'asciidtype/src/asciidtype_main.c', 29 | 'asciidtype/src/umath.c', 30 | 'asciidtype/src/umath.h', 31 | ] 32 | 33 | py.install_sources( 34 | [ 35 | 'asciidtype/__init__.py', 36 | 'asciidtype/scalar.py' 37 | ], 38 | subdir: 'asciidtype', 39 | pure: false 40 | ) 41 | 42 | py.extension_module( 43 | '_asciidtype_main', 44 | srcs, 45 | c_args: ['-g', '-O0'], 46 | install: true, 47 | subdir: 'asciidtype', 48 | include_directories: includes 49 | ) 50 | -------------------------------------------------------------------------------- /metadatadtype/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'metadatadtype', 3 | 'c', 4 | ) 5 | 6 | py_mod = import('python') 7 | py = py_mod.find_installation() 8 | 9 | incdir_numpy = run_command(py, 10 | [ 11 | '-c', 12 | 'import numpy; print(numpy.get_include())' 13 | ], 14 | check: true 15 | ).stdout().strip() 16 | 17 | includes = include_directories( 18 | [ 19 | incdir_numpy, 20 | 'metadatadtype/src' 21 | ] 22 | ) 23 | 24 | srcs = [ 25 | 'metadatadtype/src/casts.c', 26 | 'metadatadtype/src/casts.h', 27 | 'metadatadtype/src/dtype.c', 28 | 'metadatadtype/src/metadatadtype_main.c', 29 | 'metadatadtype/src/umath.c', 30 | 'metadatadtype/src/umath.h', 31 | ] 32 | 33 | py.install_sources( 34 | [ 35 | 'metadatadtype/__init__.py', 36 | 'metadatadtype/scalar.py' 37 | ], 38 | subdir: 'metadatadtype', 39 | pure: false 40 | ) 41 | 42 | py.extension_module( 43 | '_metadatadtype_main', 44 | srcs, 45 | install: true, 46 | subdir: 'metadatadtype', 47 | include_directories: includes 48 | ) 49 | -------------------------------------------------------------------------------- /unytdtype/README.md: -------------------------------------------------------------------------------- 1 | # A dtype that stores unit metadata 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. It leverages the [unyt](https://unyt.readthedocs.org) library's `Unit` 7 | type to store unit metadata, but relies on the dtype machinery to implement 8 | mathematical operations rather than an ndarray subclass like `unyt_array`. This 9 | is currently mostly for experimenting with the dtype API and is not useful for 10 | real work. 11 | 12 | ## Building 13 | 14 | Ensure Meson and NumPy are installed in the python environment you would like to use: 15 | 16 | ``` 17 | $ python3 -m pip install meson meson-python numpy build patchelf 18 | ``` 19 | 20 | Build with meson, create a wheel, and install it 21 | 22 | ``` 23 | $ rm -r dist/ 24 | $ meson build 25 | $ python -m build --wheel -Cbuilddir=build 26 | $ python -m pip install --force-reinstall dist/unytdtype*.whl 27 | ``` 28 | 29 | The `mesonpy` build backend for pip [does not currently support editable 30 | installs](https://github.com/mesonbuild/meson-python/issues/47), so `pip install 31 | -e .` will not work. 32 | -------------------------------------------------------------------------------- /asciidtype/.clang-format: -------------------------------------------------------------------------------- 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 38 | -------------------------------------------------------------------------------- /unytdtype/.clang-format: -------------------------------------------------------------------------------- 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 38 | -------------------------------------------------------------------------------- /metadatadtype/.clang-format: -------------------------------------------------------------------------------- 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 38 | -------------------------------------------------------------------------------- /stringdtype/.clang-format: -------------------------------------------------------------------------------- 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 38 | -------------------------------------------------------------------------------- /mpfdtype/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'mpfdtype', 3 | 'c', 4 | 'cpp', 5 | ) 6 | 7 | py_mod = import('python') 8 | py = py_mod.find_installation() 9 | 10 | c = meson.get_compiler('c') 11 | mpfr = c.find_library('mpfr') 12 | 13 | incdir_numpy = run_command(py, 14 | [ 15 | '-c', 16 | 'import numpy; print(numpy.get_include())' 17 | ], 18 | check: true 19 | ).stdout().strip() 20 | 21 | includes = include_directories( 22 | [ 23 | incdir_numpy, 24 | 'mpfdtype/src', 25 | ], 26 | ) 27 | 28 | srcs = [ 29 | 'mpfdtype/src/casts.cpp', 30 | 'mpfdtype/src/casts.h', 31 | 'mpfdtype/src/dtype.c', 32 | 'mpfdtype/src/dtype.h', 33 | 'mpfdtype/src/mpfdtype_main.c', 34 | 'mpfdtype/src/numbers.cpp', 35 | 'mpfdtype/src/numbers.h', 36 | 'mpfdtype/src/ops.hpp', 37 | 'mpfdtype/src/scalar.c', 38 | 'mpfdtype/src/scalar.h', 39 | 'mpfdtype/src/terrible_hacks.c', 40 | 'mpfdtype/src/terrible_hacks.h', 41 | 'mpfdtype/src/umath.cpp', 42 | 'mpfdtype/src/umath.h', 43 | ] 44 | 45 | py.install_sources( 46 | [ 47 | 'mpfdtype/__init__.py', 48 | ], 49 | subdir: 'mpfdtype', 50 | pure: false 51 | ) 52 | 53 | py.extension_module( 54 | '_mpfdtype_main', 55 | srcs, 56 | install: true, 57 | subdir: 'mpfdtype', 58 | include_directories: includes, 59 | dependencies: [mpfr], 60 | ) 61 | -------------------------------------------------------------------------------- /quaddtype/tests/test_multithreading.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | import threading 3 | 4 | import pytest 5 | 6 | import numpy as np 7 | from numpy._core import _rational_tests 8 | from numpy._core.tests.test_stringdtype import random_unicode_string_list 9 | from numpy.testing import IS_64BIT, IS_WASM 10 | from numpy.testing._private.utils import run_threaded 11 | 12 | if IS_WASM: 13 | pytest.skip(allow_module_level=True, reason="no threading support in wasm") 14 | 15 | from numpy_quaddtype import * 16 | 17 | 18 | def test_as_integer_ratio_reconstruction(): 19 | """Multi-threaded test that as_integer_ratio() can reconstruct the original value.""" 20 | 21 | def test(barrier): 22 | barrier.wait() # All threads start simultaneously 23 | values = ["3.14", "0.1", "1.414213562373095", "2.718281828459045", 24 | "-1.23456789", "1000.001", "0.0001", "1e20", "1.23e15", "1e-30", pi] 25 | for val in values: 26 | quad_val = QuadPrecision(val) 27 | num, denom = quad_val.as_integer_ratio() 28 | # todo: can remove str converstion after merging PR #213 29 | reconstructed = QuadPrecision(str(num)) / QuadPrecision(str(denom)) 30 | assert reconstructed == quad_val 31 | 32 | run_threaded(test, pass_barrier=True, max_workers=64, outer_iterations=100) -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 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: 100 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 38 | SortIncludes: Never 39 | -------------------------------------------------------------------------------- /stringdtype/README.md: -------------------------------------------------------------------------------- 1 | # A dtype that stores pointers to strings 2 | 3 | This is the prototype implementation of the variable-width UTF-8 string DType 4 | described in [NEP 55](https://numpy.org/neps/nep-0055-string_dtype.html). 5 | 6 | See the NEP for implementation details and usage examples. See 7 | `numpy.dtypes.StringDType` for the version that made it into NumPy. 8 | 9 | ## Building 10 | 11 | Ensure Meson and NumPy are installed in the python environment you would like to use: 12 | 13 | ``` 14 | $ python3 -m pip install meson meson-python 15 | ``` 16 | 17 | It is important to have the latest development version of numpy installed. 18 | Nightly wheels work well for this purpose, and can be installed easily: 19 | 20 | ```bash 21 | $ pip install -i https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy 22 | ``` 23 | 24 | You can install with `pip` directly, taking care to disable build isolation so 25 | the numpy nightly gets picked up at build time: 26 | 27 | ```bash 28 | $ pip install -v . --no-build-isolation 29 | ``` 30 | 31 | If you want to work on the `stringdtype` code, you can build with meson, 32 | create a wheel, and install it. 33 | 34 | ```bash 35 | $ rm -r dist/ 36 | $ meson build 37 | $ python -m build --wheel -Cbuilddir=build 38 | $ python -m pip install dist/path-to-wheel-file.whl 39 | ``` 40 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/quadblas_interface.h: -------------------------------------------------------------------------------- 1 | #ifndef QUADBLAS_INTERFACE_H 2 | #define QUADBLAS_INTERFACE_H 3 | 4 | #include 5 | #include 6 | #include "quad_common.h" 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | int 14 | qblas_dot(size_t n, Sleef_quad *x, size_t incx, Sleef_quad *y, size_t incy, Sleef_quad *result); 15 | 16 | int 17 | qblas_gemv(char layout, char trans, size_t m, size_t n, Sleef_quad *alpha, Sleef_quad *A, 18 | size_t lda, Sleef_quad *x, size_t incx, Sleef_quad *beta, Sleef_quad *y, size_t incy); 19 | 20 | int 21 | qblas_gemm(char layout, char transa, char transb, size_t m, size_t n, size_t k, Sleef_quad *alpha, 22 | Sleef_quad *A, size_t lda, Sleef_quad *B, size_t ldb, Sleef_quad *beta, Sleef_quad *C, 23 | size_t ldc); 24 | 25 | int 26 | qblas_supports_backend(QuadBackendType backend); 27 | 28 | PyObject * 29 | py_quadblas_set_num_threads(PyObject *self, PyObject *args); 30 | PyObject * 31 | py_quadblas_get_num_threads(PyObject *self, PyObject *args); 32 | PyObject * 33 | py_quadblas_get_version(PyObject *self, PyObject *args); 34 | 35 | int 36 | _quadblas_set_num_threads(int num_threads); 37 | int 38 | _quadblas_get_num_threads(void); 39 | 40 | #ifdef __cplusplus 41 | } 42 | #endif 43 | 44 | #endif // QUADBLAS_INTERFACE_H -------------------------------------------------------------------------------- /mpfdtype/README.md: -------------------------------------------------------------------------------- 1 | # A multi precision DType for NumPy 2 | 3 | A DType and scalar which uses [MPFR](https://www.mpfr.org/) for multi precision floating point math. MPFR itself has an LGPL license. 4 | 5 | A very basic example:: 6 | 7 | import numpy as np 8 | from mpfdtype import MPFDType, MPFloat 9 | 10 | # create an array with 200 bits precision: 11 | arr = np.arange(3).astype(MPFDType(200)) 12 | print(repr(arr)) 13 | # array(['0E+00', '1.0E+00', '2.0E+00'], dtype=MPFDType(200)) 14 | 15 | # Math uses the input precision (wraps almost all math functions): 16 | res = arr**2 + np.sqrt(arr) 17 | print(repr(res)) 18 | # array(['0E+00', '2.0E+00', 19 | # '5.4142135623730950488016887242096980785696718753769480731766784E+00'], 20 | # dtype=MPFDType(200)) 21 | 22 | # cast to a different precision: 23 | arr2 = arr.astype(MPFDType(500)) 24 | print(repr(arr2)) 25 | # array(['0E+00', '1.0E+00', '2.0E+00'], dtype=MPFDType(500)) 26 | 27 | res = arr + arr2 28 | print(repr(res)) # uses the larger precision now 29 | # array(['0E+00', '2.0E+00', '4.0E+00'], dtype=MPFDType(500)) 30 | 31 | There also is an `mpf.MPFloat(value, prec=None)`. There is no "context" 32 | as most libraries like this (including mpfr itself) typically have. 33 | The rounding mode is always the normal one. 34 | -------------------------------------------------------------------------------- /stringdtype/meson.build: -------------------------------------------------------------------------------- 1 | project( 2 | 'stringdtype', 3 | 'c', 4 | ) 5 | 6 | py_mod = import('python') 7 | py = py_mod.find_installation() 8 | 9 | incdir_numpy = run_command(py, 10 | [ 11 | '-c', 12 | 'import numpy; print(numpy.get_include())' 13 | ], 14 | check: true 15 | ).stdout().strip() 16 | 17 | cc = meson.get_compiler('c') 18 | 19 | npymath_path = incdir_numpy / '..' / 'lib' 20 | npymath_lib = cc.find_library('npymath', dirs: npymath_path) 21 | inc_np = include_directories(incdir_numpy) 22 | np_dep = declare_dependency(include_directories: inc_np) 23 | 24 | includes = include_directories( 25 | [ 26 | incdir_numpy, 27 | 'stringdtype/src' 28 | ] 29 | ) 30 | 31 | srcs = [ 32 | 'stringdtype/src/casts.c', 33 | 'stringdtype/src/casts.h', 34 | 'stringdtype/src/dtype.c', 35 | 'stringdtype/src/main.c', 36 | 'stringdtype/src/static_string.c', 37 | 'stringdtype/src/static_string.h', 38 | 'stringdtype/src/umath.c', 39 | 'stringdtype/src/umath.h', 40 | ] 41 | 42 | py.install_sources( 43 | [ 44 | 'stringdtype/__init__.py', 45 | 'stringdtype/scalar.py', 46 | ], 47 | subdir: 'stringdtype', 48 | pure: false 49 | ) 50 | 51 | py.extension_module( 52 | '_main', 53 | srcs, 54 | install: true, 55 | subdir: 'stringdtype', 56 | include_directories: includes, 57 | dependencies: [np_dep, npymath_lib] 58 | ) 59 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/mpfdtype_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL MPFDType_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL MPFDType_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #include "numpy/arrayobject.h" 7 | #include "numpy/ufuncobject.h" 8 | #include "numpy/dtype_api.h" 9 | 10 | #include "dtype.h" 11 | #include "umath.h" 12 | #include "terrible_hacks.h" 13 | 14 | static struct PyModuleDef moduledef = { 15 | PyModuleDef_HEAD_INIT, 16 | .m_name = "mpfdtype_main", 17 | .m_size = -1, 18 | }; 19 | 20 | /* Module initialization function */ 21 | PyMODINIT_FUNC 22 | PyInit__mpfdtype_main(void) 23 | { 24 | import_array(); 25 | import_umath(); 26 | 27 | PyObject *m = PyModule_Create(&moduledef); 28 | if (m == NULL) { 29 | return NULL; 30 | } 31 | 32 | if (init_mpf_scalar() < 0) { 33 | goto error; 34 | } 35 | 36 | if (PyModule_AddObject(m, "MPFloat", (PyObject *)&MPFloat_Type) < 0) { 37 | goto error; 38 | } 39 | 40 | if (init_mpf_dtype() < 0) { 41 | goto error; 42 | } 43 | 44 | if (PyModule_AddObject(m, "MPFDType", (PyObject *)&MPFDType) < 0) { 45 | goto error; 46 | } 47 | 48 | if (init_mpf_umath() == -1) { 49 | goto error; 50 | } 51 | 52 | if (init_terrible_hacks() < 0) { 53 | goto error; 54 | } 55 | 56 | return m; 57 | 58 | error: 59 | Py_DECREF(m); 60 | return NULL; 61 | } 62 | -------------------------------------------------------------------------------- /unytdtype/unytdtype/src/unytdtype_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL unytdtype_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL unytdtype_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #include "numpy/arrayobject.h" 7 | #include "numpy/ufuncobject.h" 8 | #include "numpy/dtype_api.h" 9 | 10 | #include "dtype.h" 11 | #include "umath.h" 12 | 13 | static struct PyModuleDef moduledef = { 14 | PyModuleDef_HEAD_INIT, 15 | .m_name = "unytdtype_main", 16 | .m_size = -1, 17 | }; 18 | 19 | /* Module initialization function */ 20 | PyMODINIT_FUNC 21 | PyInit__unytdtype_main(void) 22 | { 23 | import_array(); 24 | import_umath(); 25 | 26 | PyObject *m = PyModule_Create(&moduledef); 27 | if (m == NULL) { 28 | return NULL; 29 | } 30 | 31 | PyObject *mod = PyImport_ImportModule("unytdtype"); 32 | if (mod == NULL) { 33 | goto error; 34 | } 35 | UnytScalar_Type = 36 | (PyTypeObject *)PyObject_GetAttrString(mod, "UnytScalar"); 37 | Py_DECREF(mod); 38 | 39 | if (UnytScalar_Type == NULL) { 40 | goto error; 41 | } 42 | 43 | if (init_unyt_dtype() < 0) { 44 | goto error; 45 | } 46 | 47 | if (PyModule_AddObject(m, "UnytDType", (PyObject *)&UnytDType) < 0) { 48 | goto error; 49 | } 50 | 51 | if (init_multiply_ufunc() == -1) { 52 | goto error; 53 | } 54 | 55 | return m; 56 | 57 | error: 58 | Py_DECREF(m); 59 | return NULL; 60 | } 61 | -------------------------------------------------------------------------------- /asv_benchmarks/benchmarks/strings.py: -------------------------------------------------------------------------------- 1 | # Write the benchmarking functions here. 2 | # See "Writing benchmarks" in the asv docs for more information. 3 | import uuid 4 | 5 | import numpy as np 6 | 7 | from asciidtype import ASCIIDType 8 | from stringdtype import StringDType 9 | 10 | 11 | def generate_data(n=100000): 12 | """Generate data for the benchmarks. 13 | 14 | The vast majority of the time spent benchmarking is generating data; generate it 15 | once and store it to avoid having to do this every run. 16 | """ 17 | strings_list = [str(uuid.uuid4()) + "\n" for i in range(n)] 18 | 19 | with open("strings", "w") as f: 20 | f.writelines(strings_list) 21 | 22 | 23 | class TimeASCIIDType: 24 | def setup(self): 25 | self.ascii_dtype_object = ASCIIDType(36) 26 | with open("strings", "r") as f: 27 | self.strings = f.readlines() 28 | 29 | def time_allocate(self): 30 | _ = np.array(self.strings, dtype=self.ascii_dtype_object) 31 | 32 | 33 | class TimeStringDType: 34 | def setup(self): 35 | self.string_dtype_object = StringDType() 36 | with open("strings", "r") as f: 37 | self.strings = f.readlines() 38 | 39 | def time_allocate(self): 40 | _ = np.array(self.strings, dtype=self.string_dtype_object) 41 | 42 | 43 | class TimeObjectDType: 44 | def setup(self): 45 | with open("strings", "r") as f: 46 | self.strings = f.readlines() 47 | 48 | def time_allocate(self): 49 | _ = np.array(self.strings, dtype=object) 50 | -------------------------------------------------------------------------------- /asciidtype/asciidtype/src/asciidtype_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL asciidtype_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL asciidtype_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 7 | #include "numpy/ndarraytypes.h" 8 | #include "numpy/arrayobject.h" 9 | #include "numpy/ufuncobject.h" 10 | #include "numpy/dtype_api.h" 11 | 12 | #include "dtype.h" 13 | #include "umath.h" 14 | 15 | static struct PyModuleDef moduledef = { 16 | PyModuleDef_HEAD_INIT, 17 | .m_name = "asciidtype_main", 18 | .m_size = -1, 19 | }; 20 | 21 | /* Module initialization function */ 22 | PyMODINIT_FUNC 23 | PyInit__asciidtype_main(void) 24 | { 25 | import_array(); 26 | import_umath(); 27 | 28 | PyObject *m = PyModule_Create(&moduledef); 29 | if (m == NULL) { 30 | return NULL; 31 | } 32 | 33 | PyObject *mod = PyImport_ImportModule("asciidtype"); 34 | if (mod == NULL) { 35 | goto error; 36 | } 37 | ASCIIScalar_Type = 38 | (PyTypeObject *)PyObject_GetAttrString(mod, "ASCIIScalar"); 39 | Py_DECREF(mod); 40 | 41 | if (ASCIIScalar_Type == NULL) { 42 | goto error; 43 | } 44 | 45 | if (init_ascii_dtype() < 0) { 46 | goto error; 47 | } 48 | 49 | if (PyModule_AddObject(m, "ASCIIDType", (PyObject *)&ASCIIDType) < 0) { 50 | goto error; 51 | } 52 | 53 | if (init_ufuncs() == -1) { 54 | goto error; 55 | } 56 | 57 | return m; 58 | 59 | error: 60 | Py_DECREF(m); 61 | return NULL; 62 | } 63 | -------------------------------------------------------------------------------- /metadatadtype/metadatadtype/src/metadatadtype_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL metadatadtype_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL metadatadtype_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 7 | #include "numpy/arrayobject.h" 8 | #include "numpy/ufuncobject.h" 9 | #include "numpy/dtype_api.h" 10 | 11 | #include "umath.h" 12 | #include "dtype.h" 13 | 14 | static struct PyModuleDef moduledef = { 15 | PyModuleDef_HEAD_INIT, 16 | .m_name = "metadatadtype_main", 17 | .m_size = -1, 18 | }; 19 | 20 | /* Module initialization function */ 21 | PyMODINIT_FUNC 22 | PyInit__metadatadtype_main(void) 23 | { 24 | import_array(); 25 | import_umath(); 26 | 27 | PyObject *m = PyModule_Create(&moduledef); 28 | if (m == NULL) { 29 | return NULL; 30 | } 31 | 32 | PyObject *mod = PyImport_ImportModule("metadatadtype"); 33 | if (mod == NULL) { 34 | goto error; 35 | } 36 | MetadataScalar_Type = 37 | (PyTypeObject *)PyObject_GetAttrString(mod, "MetadataScalar"); 38 | Py_DECREF(mod); 39 | 40 | if (MetadataScalar_Type == NULL) { 41 | goto error; 42 | } 43 | 44 | if (init_metadata_dtype() < 0) { 45 | goto error; 46 | } 47 | 48 | if (PyModule_AddObject(m, "MetadataDType", (PyObject *)&MetadataDType) < 49 | 0) { 50 | goto error; 51 | } 52 | 53 | if (init_ufuncs() == -1) { 54 | goto error; 55 | } 56 | 57 | return m; 58 | 59 | error: 60 | Py_DECREF(m); 61 | return NULL; 62 | } 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, NumPy Developers. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of the NumPy Developers nor the names of any 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /.github/workflows/typecheck.yml: -------------------------------------------------------------------------------- 1 | name: Validate static types in quaddtype 2 | permissions: read-all 3 | 4 | on: 5 | pull_request: 6 | paths: 7 | - .github/workflows/typecheck.yml 8 | - quaddtype/numpy_quaddtype/** 9 | - quaddtype/meson.build 10 | - quaddtype/pyproject.toml 11 | workflow_dispatch: 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | typecheck_quaddtype: 19 | runs-on: ubuntu-latest 20 | timeout-minutes: 2 21 | 22 | steps: 23 | - uses: actions/checkout@v6 24 | 25 | - uses: astral-sh/setup-uv@v7.1.5 26 | with: 27 | activate-environment: true 28 | python-version: "3.11" 29 | 30 | - name: install 31 | working-directory: quaddtype 32 | run: | 33 | uv pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy 34 | uv pip install meson>=1.3.2 meson-python wheel ninja 35 | uv pip install mypy pyright . --no-build-isolation 36 | 37 | - name: pyright 38 | working-directory: quaddtype 39 | run: pyright 40 | 41 | - name: pyright --verifytypes 42 | working-directory: quaddtype 43 | run: pyright --ignoreexternal --verifytypes numpy_quaddtype 44 | 45 | - name: mypy 46 | working-directory: quaddtype 47 | run: mypy --no-incremental --cache-dir=/dev/null . 48 | 49 | - name: stubtest 50 | working-directory: quaddtype 51 | run: stubtest --mypy-config-file pyproject.toml numpy_quaddtype 52 | -------------------------------------------------------------------------------- /quaddtype/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, NumPy Developers. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of the NumPy Developers nor the names of any 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /quaddtype/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "meson>=1.3.2", 4 | "meson-python>=0.18.0", 5 | "wheel", 6 | "numpy>=2.0" 7 | ] 8 | build-backend = "mesonpy" 9 | 10 | [project] 11 | name = "numpy_quaddtype" 12 | description = "Quad (128-bit) float dtype for numpy" 13 | version = "0.2.0" 14 | readme = 'README.md' 15 | license = "BSD-3-Clause" 16 | license-files = ["LICENSE"] 17 | authors = [{name = "Swayam Singh", email = "singhswayam008@gmail.com"}] 18 | classifiers = [ 19 | "Development Status :: 4 - Beta", 20 | "Programming Language :: Python :: 3", 21 | "Programming Language :: Python :: 3.11", 22 | "Programming Language :: Python :: 3.12", 23 | "Programming Language :: Python :: 3.13", 24 | "Programming Language :: Python :: 3.14", 25 | "Programming Language :: Python :: Free Threading", 26 | "Typing :: Typed", 27 | ] 28 | requires-python = ">=3.11.0" 29 | dependencies = [ 30 | "numpy>=2.0" 31 | ] 32 | 33 | [project.optional-dependencies] 34 | test = [ 35 | "pytest", 36 | "mpmath", 37 | "pytest-run-parallel" 38 | ] 39 | 40 | [project.urls] 41 | Repository = "https://github.com/numpy/numpy-user-dtypes" 42 | Documentation = "https://github.com/numpy/numpy-user-dtypes/tree/main/quaddtype" 43 | Issues = "https://github.com/numpy/numpy-user-dtypes/issues" 44 | 45 | [tool.pyright] 46 | include = ["numpy_quaddtype/*.pyi"] 47 | typeCheckingMode = "strict" 48 | enableTypeIgnoreComments = false 49 | reportImplicitOverride = true 50 | reportUnnecessaryTypeIgnoreComment = true 51 | 52 | [tool.mypy] 53 | strict = true 54 | strict_equality_for_none = true 55 | exclude = ["build", "numpy_quaddtype/src", "subprojects", "tests"] 56 | enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] 57 | warn_unreachable = false 58 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/__init__.pyi: -------------------------------------------------------------------------------- 1 | from typing import Final 2 | import enum 3 | 4 | from ._quaddtype_main import ( 5 | QuadPrecDType, 6 | QuadPrecision, 7 | _IntoQuad, # type-check only # pyright: ignore[reportPrivateUsage] 8 | get_num_threads, 9 | get_quadblas_version, 10 | is_longdouble_128, 11 | set_num_threads, 12 | ) 13 | 14 | class QuadBackend(enum.IntEnum): 15 | SLEEF = 0 16 | LONGDOUBLE = 1 17 | 18 | __all__ = [ 19 | "QuadPrecision", 20 | "QuadPrecDType", 21 | "QuadBackend", 22 | "SleefQuadPrecision", 23 | "LongDoubleQuadPrecision", 24 | "SleefQuadPrecDType", 25 | "LongDoubleQuadPrecDType", 26 | "is_longdouble_128", 27 | "pi", 28 | "e", 29 | "log2e", 30 | "log10e", 31 | "ln2", 32 | "ln10", 33 | "max_value", 34 | "epsilon", 35 | "smallest_normal", 36 | "smallest_subnormal", 37 | "bits", 38 | "precision", 39 | "resolution", 40 | "set_num_threads", 41 | "get_num_threads", 42 | "get_quadblas_version", 43 | ] 44 | 45 | __version__: Final[str] = ... 46 | 47 | def SleefQuadPrecision(value: _IntoQuad) -> QuadPrecision: ... 48 | def LongDoubleQuadPrecision(value: _IntoQuad) -> QuadPrecision: ... 49 | def SleefQuadPrecDType() -> QuadPrecDType: ... 50 | def LongDoubleQuadPrecDType() -> QuadPrecDType: ... 51 | 52 | pi: Final[QuadPrecision] = ... 53 | e: Final[QuadPrecision] = ... 54 | log2e: Final[QuadPrecision] = ... 55 | log10e: Final[QuadPrecision] = ... 56 | ln2: Final[QuadPrecision] = ... 57 | ln10: Final[QuadPrecision] = ... 58 | max_value: Final[QuadPrecision] = ... 59 | epsilon: Final[QuadPrecision] = ... 60 | smallest_normal: Final[QuadPrecision] = ... 61 | smallest_subnormal: Final[QuadPrecision] = ... 62 | resolution: Final[QuadPrecision] = ... 63 | bits: Final = 128 64 | precision: Final = 33 65 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/__init__.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | from ._quaddtype_main import ( 4 | QuadPrecision, 5 | QuadPrecDType, 6 | is_longdouble_128, 7 | get_sleef_constant, 8 | set_num_threads, 9 | get_num_threads, 10 | get_quadblas_version 11 | ) 12 | 13 | __version__ = "0.2.0" 14 | 15 | 16 | class QuadBackend(enum.IntEnum): 17 | """Backend type for QuadPrecision computations.""" 18 | SLEEF = 0 19 | LONGDOUBLE = 1 20 | 21 | 22 | __all__ = [ 23 | 'QuadPrecision', 'QuadPrecDType', 'QuadBackend', 24 | 'SleefQuadPrecision', 'LongDoubleQuadPrecision', 25 | 'SleefQuadPrecDType', 'LongDoubleQuadPrecDType', 'is_longdouble_128', 26 | # Constants 27 | 'pi', 'e', 'log2e', 'log10e', 'ln2', 'ln10', 'max_value', 'epsilon', 28 | 'smallest_normal', 'smallest_subnormal', 'bits', 'precision', 'resolution', 29 | # QuadBLAS related functions 30 | 'set_num_threads', 'get_num_threads', 'get_quadblas_version' 31 | ] 32 | 33 | def SleefQuadPrecision(value): 34 | return QuadPrecision(value, backend='sleef') 35 | 36 | def LongDoubleQuadPrecision(value): 37 | return QuadPrecision(value, backend='longdouble') 38 | 39 | def SleefQuadPrecDType(): 40 | return QuadPrecDType(backend='sleef') 41 | 42 | def LongDoubleQuadPrecDType(): 43 | return QuadPrecDType(backend='longdouble') 44 | 45 | pi = get_sleef_constant("pi") 46 | e = get_sleef_constant("e") 47 | log2e = get_sleef_constant("log2e") 48 | log10e = get_sleef_constant("log10e") 49 | ln2 = get_sleef_constant("ln2") 50 | ln10 = get_sleef_constant("ln10") 51 | max_value = get_sleef_constant("max_value") 52 | epsilon = get_sleef_constant("epsilon") 53 | smallest_normal = get_sleef_constant("smallest_normal") 54 | smallest_subnormal = get_sleef_constant("smallest_subnormal") 55 | bits = get_sleef_constant("bits") 56 | precision = get_sleef_constant("precision") 57 | resolution = get_sleef_constant("resolution") 58 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/dtype.h: -------------------------------------------------------------------------------- 1 | #ifndef _MPRFDTYPE_DTYPE_H 2 | #define _MPRFDTYPE_DTYPE_H 3 | 4 | #include "mpfr.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | #include "scalar.h" 11 | 12 | 13 | typedef struct { 14 | PyArray_Descr base; 15 | mpfr_prec_t precision; 16 | } MPFDTypeObject; 17 | 18 | 19 | /* 20 | * It would be more compat to just store the kind, exponent and signfificand, 21 | * however. For in-place operations mpfr needs cannot share the same 22 | * significand for multiple ops (but can have an op repeat). 23 | * So not storeing only those (saving 16 bytes of 48 for a 128 bit number) 24 | * removes the need to worry about this. 25 | */ 26 | typedef mpfr_t mpf_storage; 27 | 28 | extern PyArray_DTypeMeta MPFDType; 29 | 30 | 31 | /* 32 | * Load into an mpfr_ptr, use a macro which may allow easier changing back 33 | * to a compact storage scheme. 34 | */ 35 | static inline void 36 | _mpf_load(mpfr_ptr *x, char *data_ptr, mpfr_prec_t precision) { 37 | x[0] = (mpfr_ptr)data_ptr; 38 | /* 39 | * We must ensure the signficand is initialized, but NumPy only ensures 40 | * everything is NULL'ed. 41 | */ 42 | if (mpfr_custom_get_significand(x[0]) == NULL) { 43 | void *signficand = data_ptr + sizeof(mpf_storage); 44 | mpfr_custom_init(signficand, precision); 45 | mpfr_custom_init_set(x[0], MPFR_NAN_KIND, 0, precision, signficand); 46 | } 47 | } 48 | #define mpf_load(x, data_ptr, precision) _mpf_load(&x, data_ptr, precision) 49 | 50 | 51 | 52 | /* 53 | * Not actually required in the current scheme, but keep for now. 54 | * (I had a more compat storage scheme at some point.) 55 | */ 56 | static inline void 57 | mpf_store(char *data_ptr, mpfr_t x) { 58 | assert(data_ptr == (char *)x); 59 | } 60 | 61 | 62 | MPFDTypeObject * 63 | new_MPFDType_instance(mpfr_prec_t precision); 64 | 65 | int 66 | init_mpf_dtype(void); 67 | 68 | #ifdef __cplusplus 69 | } 70 | #endif 71 | 72 | #endif /*_MPRFDTYPE_DTYPE_H*/ 73 | -------------------------------------------------------------------------------- /metadatadtype/tests/test_metadatadtype.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from metadatadtype import MetadataDType, MetadataScalar 4 | 5 | 6 | def test_dtype_creation(): 7 | dtype = MetadataDType("some metadata") 8 | assert str(dtype) == "MetadataDType('some metadata')" 9 | 10 | 11 | def test_creation_from_zeros(): 12 | dtype = MetadataDType("test") 13 | arr = np.zeros(3, dtype=dtype) 14 | assert str(arr) == "[0.0 test 0.0 test 0.0 test]" 15 | 16 | 17 | def test_creation_from_list(): 18 | dtype = MetadataDType("test") 19 | arr = np.array([0, 0, 0], dtype=dtype) 20 | assert str(arr) == "[0.0 test 0.0 test 0.0 test]" 21 | 22 | 23 | def test_creation_from_scalar(): 24 | dtype = MetadataDType("test") 25 | scalar = MetadataScalar(1, dtype) 26 | arr = np.array([scalar, scalar, scalar]) 27 | assert str(arr) == "[1.0 test 1.0 test 1.0 test]" 28 | 29 | 30 | def test_multiplication(): 31 | dtype = MetadataDType("test") 32 | scalar = MetadataScalar(1, dtype) 33 | arr = np.array([scalar, scalar, scalar]) 34 | scalar = MetadataScalar(2, dtype) 35 | arr2 = np.array([scalar, scalar, scalar]) 36 | res = arr * arr2 37 | assert str(res) == "[2.0 test 2.0 test 2.0 test]" 38 | 39 | 40 | def test_isnan(): 41 | dtype = MetadataDType("test") 42 | num_scalar = MetadataScalar(1, dtype) 43 | nan_scalar = MetadataScalar(np.nan, dtype) 44 | arr = np.array([num_scalar, nan_scalar, nan_scalar]) 45 | np.testing.assert_array_equal(np.isnan(arr), np.array([False, True, True])) 46 | 47 | 48 | def test_cast_to_different_metadata(): 49 | dtype = MetadataDType("test") 50 | scalar = MetadataScalar(1, dtype) 51 | arr = np.array([scalar, scalar, scalar]) 52 | dtype2 = MetadataDType("test2") 53 | conv = arr.astype(dtype2) 54 | assert str(conv) == "[1.0 test2 1.0 test2 1.0 test2]" 55 | 56 | 57 | def test_cast_to_float64(): 58 | dtype = MetadataDType("test") 59 | scalar = MetadataScalar(1, dtype) 60 | arr = np.array([scalar, scalar, scalar]) 61 | conv = arr.astype("float64") 62 | assert str(conv) == "[1. 1. 1.]" 63 | 64 | 65 | def test_is_numeric(): 66 | assert MetadataDType._is_numeric 67 | -------------------------------------------------------------------------------- /unytdtype/tests/test_unytdtype.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import unyt 3 | 4 | from unytdtype import UnytDType, UnytScalar 5 | 6 | 7 | def test_dtype_creation(): 8 | dtype = UnytDType("m") 9 | assert str(dtype) == "UnytDType('m')" 10 | 11 | dtype2 = UnytDType(unyt.Unit("m")) 12 | assert str(dtype2) == "UnytDType('m')" 13 | assert dtype == dtype2 14 | 15 | 16 | def test_scalar_creation(): 17 | dtype = UnytDType("m") 18 | unit = unyt.Unit("m") 19 | unit_s = "m" 20 | 21 | s_1 = UnytScalar(1, dtype) 22 | s_2 = UnytScalar(1, unit) 23 | s_3 = UnytScalar(1, unit_s) 24 | 25 | assert s_1 == s_2 == s_3 26 | 27 | 28 | def test_creation_from_zeros(): 29 | dtype = UnytDType("m") 30 | arr = np.zeros(3, dtype=dtype) 31 | assert str(arr) == "[0.0 m 0.0 m 0.0 m]" 32 | 33 | 34 | def test_creation_from_list(): 35 | dtype = UnytDType("m") 36 | arr = np.array([0, 0, 0], dtype=dtype) 37 | assert str(arr) == "[0.0 m 0.0 m 0.0 m]" 38 | 39 | 40 | def test_creation_from_scalar(): 41 | meter = UnytScalar(1, unyt.m) 42 | arr = np.array([meter, meter, meter]) 43 | assert str(arr) == "[1.0 m 1.0 m 1.0 m]" 44 | 45 | 46 | def test_multiplication(): 47 | meter = UnytScalar(1, unyt.m) 48 | arr = np.array([meter, meter, meter]) 49 | arr2 = np.array([2 * meter, 2 * meter, 2 * meter]) 50 | res = arr * arr2 51 | assert str(res) == "[2.0 m**2 2.0 m**2 2.0 m**2]" 52 | 53 | 54 | def test_cast_to_different_unit(): 55 | meter = UnytScalar(1, unyt.m) 56 | arr = np.array([meter, meter, meter]) 57 | conv = arr.astype(UnytDType("cm")) 58 | assert str(conv) == "[100.0 cm 100.0 cm 100.0 cm]" 59 | 60 | 61 | def test_insert_with_different_unit(): 62 | meter = UnytScalar(1, unyt.m) 63 | cm = UnytScalar(1, unyt.cm) 64 | arr = np.array([meter, meter, meter]) 65 | arr[0] = cm 66 | assert str(arr) == "[0.01 m 1.0 m 1.0 m]" 67 | 68 | 69 | def test_cast_to_float64(): 70 | meter = UnytScalar(1, unyt.m) 71 | arr = np.array([meter, meter, meter]) 72 | conv = arr.astype("float64") 73 | assert str(conv) == "[1. 1. 1.]" 74 | 75 | 76 | def test_is_numeric(): 77 | assert UnytDType._is_numeric 78 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/dragon4.h: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_DRAGON4_H 2 | #define _QUADDTYPE_DRAGON4_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include "numpy/arrayobject.h" 10 | #include 11 | #include "quad_common.h" 12 | 13 | typedef enum DigitMode 14 | { 15 | /* Round digits to print shortest uniquely identifiable number. */ 16 | DigitMode_Unique, 17 | /* Output the digits of the number as if with infinite precision */ 18 | DigitMode_Exact, 19 | } DigitMode; 20 | 21 | typedef enum CutoffMode 22 | { 23 | /* up to cutoffNumber significant digits */ 24 | CutoffMode_TotalLength, 25 | /* up to cutoffNumber significant digits past the decimal point */ 26 | CutoffMode_FractionLength, 27 | } CutoffMode; 28 | 29 | typedef enum TrimMode 30 | { 31 | TrimMode_None, /* don't trim zeros, always leave a decimal point */ 32 | TrimMode_LeaveOneZero, /* trim all but the zero before the decimal point */ 33 | TrimMode_Zeros, /* trim all trailing zeros, leave decimal point */ 34 | TrimMode_DptZeros, /* trim trailing zeros & trailing decimal point */ 35 | } TrimMode; 36 | 37 | typedef struct { 38 | int scientific; 39 | DigitMode digit_mode; 40 | CutoffMode cutoff_mode; 41 | int precision; 42 | int min_digits; 43 | int sign; 44 | TrimMode trim_mode; 45 | int digits_left; 46 | int digits_right; 47 | int exp_digits; 48 | } Dragon4_Options; 49 | 50 | PyObject *Dragon4_Positional_QuadDType(Sleef_quad *val, DigitMode digit_mode, 51 | CutoffMode cutoff_mode, int precision, int min_digits, 52 | int sign, TrimMode trim, int pad_left, int pad_right); 53 | 54 | PyObject *Dragon4_Scientific_QuadDType(Sleef_quad *val, DigitMode digit_mode, 55 | int precision, int min_digits, int sign, TrimMode trim, 56 | int pad_left, int exp_digits); 57 | 58 | PyObject *Dragon4_Positional(PyObject *obj, DigitMode digit_mode, 59 | CutoffMode cutoff_mode, int precision, int min_digits, 60 | int sign, TrimMode trim, int pad_left, int pad_right); 61 | 62 | PyObject *Dragon4_Scientific(PyObject *obj, DigitMode digit_mode, int precision, 63 | int min_digits, int sign, TrimMode trim, int pad_left, 64 | int exp_digits); 65 | 66 | #ifdef __cplusplus 67 | } 68 | #endif 69 | 70 | #endif /* _QUADDTYPE_DRAGON4_H */ -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/terrible_hacks.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL MPFDType_ARRAY_API 4 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 5 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 6 | #define NO_IMPORT_ARRAY 7 | #include "numpy/arrayobject.h" 8 | #include "numpy/ndarraytypes.h" 9 | #include "numpy/dtype_api.h" 10 | 11 | #include "mpfr.h" 12 | 13 | #include "dtype.h" 14 | #include "scalar.h" 15 | /* 16 | * Some terrible hacks to make things work that need to be improved in NumPy. 17 | */ 18 | 19 | 20 | /* 21 | * A previous verion had a more tricky copyswap, but really we can just 22 | * copy the itemsize, needed because NumPy still uses it occasionally 23 | * (for larger itemsizes at least). 24 | */ 25 | static void 26 | copyswap_mpf(char *dst, char *src, int swap, PyArrayObject *ap) 27 | { 28 | /* Note that it is probably better to only get the descr from `ap` */ 29 | PyArray_Descr *descr = PyArray_DESCR(ap); 30 | 31 | /* copy data and then fix significand (could also do same as cast...) */ 32 | memcpy(dst, src, descr->elsize); 33 | // TODO: To support unaligned data, only need to do this if it is aligned: 34 | mpfr_custom_move((mpfr_ptr)dst, ((mpf_field *)dst)->significand); 35 | } 36 | 37 | 38 | /* Should only be used for sorting, so more complex than necessary, probably */ 39 | int compare_mpf(char *in1_ptr, char *in2_ptr, int swap, PyArrayObject *ap) 40 | { 41 | /* Note that it is probably better to only get the descr from `ap` */ 42 | mpfr_prec_t precision = ((MPFDTypeObject *)PyArray_DESCR(ap))->precision; 43 | 44 | mpfr_ptr in1, in2; 45 | 46 | mpf_load(in1, in1_ptr, precision); 47 | mpf_load(in2, in2_ptr, precision); 48 | 49 | if (!mpfr_total_order_p(in1, in2)) { 50 | return 1; 51 | } 52 | if (!mpfr_total_order_p(in2, in1)) { 53 | return -1; 54 | } 55 | return 0; 56 | } 57 | 58 | 59 | int 60 | init_terrible_hacks(void) { 61 | /* Defaults to -1 byt ISNUMBER misfires for it, so use MAX */ 62 | MPFDType.type_num = NPY_MAX_INT; 63 | 64 | /* 65 | * Add a some ->f slots the terrible way: 66 | */ 67 | MPFDTypeObject *descr = new_MPFDType_instance(10); 68 | if (descr == NULL) { 69 | return -1; 70 | } 71 | /* ->f slots are the same for all instances (currently). */ 72 | PyDataType_GetArrFuncs(&descr->base)->copyswap = (PyArray_CopySwapFunc *)©swap_mpf; 73 | PyDataType_GetArrFuncs(&descr->base)->compare = (PyArray_CompareFunc *)&compare_mpf; 74 | Py_DECREF(descr); 75 | 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /quaddtype/subprojects/packagefiles/sleef/meson.build: -------------------------------------------------------------------------------- 1 | project('sleef', version: '3.8') 2 | 3 | cmake = find_program('cmake') 4 | ninja = find_program('ninja', 'make', required: false) 5 | 6 | sleef_build_dir = 'sleef_build' 7 | sleef_install_dir = 'sleef_install' 8 | 9 | # turning off parallel build in windows 10 | parallel_flag = ['--parallel'] 11 | if host_machine.system() == 'windows' 12 | parallel_flag = [] 13 | endif 14 | 15 | # For building sleef with TSan, delete the sleef subproject and follow the README instructions to build sleef externally. 16 | sleef_configure = run_command([ 17 | cmake, 18 | '-S', meson.current_source_dir(), 19 | '-B', meson.current_build_dir() / sleef_build_dir, 20 | '-DCMAKE_BUILD_TYPE=Release', 21 | '-DSLEEF_BUILD_QUAD=ON', 22 | '-DSLEEF_BUILD_SHARED_LIBS=OFF', 23 | '-DSLEEF_BUILD_TESTS=OFF', 24 | '-DSLEEF_BUILD_INLINE_HEADERS=OFF', 25 | '-DCMAKE_POSITION_INDEPENDENT_CODE=ON', 26 | '-DCMAKE_INSTALL_PREFIX=' + meson.current_build_dir() / sleef_install_dir 27 | ], check: false, capture: true) 28 | 29 | if sleef_configure.returncode() != 0 30 | error('SLEEF CMake configuration failed: ' + sleef_configure.stderr()) 31 | endif 32 | 33 | sleef_build_target = custom_target('sleef_build', 34 | command: [cmake, '--build', meson.current_build_dir() / sleef_build_dir, '--target', 'install'] + parallel_flag, 35 | output: 'sleef_built.stamp', 36 | console: true, 37 | build_always_stale: true, 38 | build_by_default: true 39 | ) 40 | 41 | sleef_include_path = meson.current_build_dir() / sleef_install_dir / 'include' 42 | 43 | sleef_build_dep = declare_dependency(sources: [sleef_build_target]) 44 | 45 | sleef_static_define = '' 46 | if host_machine.system() == 'windows' 47 | sleef_static_define = '-DSLEEF_STATIC_LIBS' 48 | endif 49 | 50 | compile_args_list = ['-I' + sleef_include_path] 51 | if sleef_static_define != '' 52 | compile_args_list += sleef_static_define 53 | endif 54 | 55 | sleef_dep = declare_dependency( 56 | dependencies: [sleef_build_dep], 57 | compile_args: compile_args_list, 58 | link_args: ['-L' + meson.current_build_dir() / sleef_install_dir / 'lib', '-L' + meson.current_build_dir() / sleef_install_dir / 'lib64', '-lsleef'] #both lib and lib64 because of ubuntu vs redhat compatibility 59 | ) 60 | 61 | sleefquad_dep = declare_dependency( 62 | dependencies: [sleef_build_dep], 63 | compile_args: compile_args_list, 64 | link_args: ['-L' + meson.current_build_dir() / sleef_install_dir / 'lib', '-L' + meson.current_build_dir() / sleef_install_dir / 'lib64', '-lsleefquad'] 65 | ) -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/utilities.h: -------------------------------------------------------------------------------- 1 | #ifndef QUAD_UTILITIES_H 2 | #define QUAD_UTILITIES_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "quad_common.h" 9 | #include 10 | #include 11 | #include 12 | 13 | int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value, char **endptr, bool require_full_parse); 14 | int ascii_isspace(int c); 15 | int ascii_isalpha(char c); 16 | int ascii_isdigit(char c); 17 | int ascii_isalnum(char c); 18 | int ascii_tolower(int c); 19 | int ascii_strncasecmp(const char *s1, const char *s2, size_t n); 20 | 21 | // Locale-independent ASCII string to quad parser (inspired by NumPyOS_ascii_strtold) 22 | int NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_value, char **endptr); 23 | 24 | 25 | // Helper function: Convert quad_value to Sleef_quad for Dragon4 26 | Sleef_quad 27 | quad_to_sleef_quad(const quad_value *in_val, QuadBackendType backend); 28 | 29 | #ifdef __cplusplus 30 | } 31 | 32 | #include 33 | 34 | // Load a value from memory, handling alignment 35 | template 36 | static inline T 37 | load(const char *ptr) 38 | { 39 | if constexpr (Aligned) { 40 | return *(const T *)ptr; 41 | } 42 | else { 43 | T val; 44 | std::memcpy(&val, ptr, sizeof(T)); 45 | return val; 46 | } 47 | } 48 | 49 | // Store a value to memory, handling alignment 50 | template 51 | static inline void 52 | store(char *ptr, T val) 53 | { 54 | if constexpr (Aligned) { 55 | *(T *)ptr = val; 56 | } 57 | else { 58 | std::memcpy(ptr, &val, sizeof(T)); 59 | } 60 | } 61 | 62 | // Load quad_value from memory based on backend and alignment 63 | template 64 | static inline quad_value 65 | load_quad(const char *ptr, QuadBackendType backend) 66 | { 67 | quad_value val; 68 | if (backend == BACKEND_SLEEF) { 69 | val.sleef_value = load(ptr); 70 | } 71 | else { 72 | val.longdouble_value = load(ptr); 73 | } 74 | return val; 75 | } 76 | 77 | // Store quad_value to memory based on backend and alignment 78 | template 79 | static inline void 80 | store_quad(char *ptr, const quad_value &val, QuadBackendType backend) 81 | { 82 | if (backend == BACKEND_SLEEF) { 83 | store(ptr, val.sleef_value); 84 | } 85 | else { 86 | store(ptr, val.longdouble_value); 87 | } 88 | } 89 | 90 | #endif // __cplusplus 91 | 92 | #endif // QUAD_UTILITIES_H -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | .mesonpy-native-file.ini 132 | compile_commands.json 133 | 134 | .ruff-cache/ 135 | .asv 136 | .vscode/ 137 | *.whl 138 | .DS_Store 139 | .idea/ 140 | 141 | # quaddtype 142 | /quaddtype/subprojects/qblas/ 143 | /quaddtype/subprojects/sleef/ 144 | .wraplock 145 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/promoters.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _QUADDTYPE_PROMOTERS 2 | #define _QUADDTYPE_PROMOTERS 3 | 4 | #include 5 | #include 6 | #include 7 | #include "numpy/arrayobject.h" 8 | #include "numpy/ndarrayobject.h" 9 | #include "numpy/ufuncobject.h" 10 | #include "numpy/dtype_api.h" 11 | 12 | #include "../dtype.h" 13 | 14 | inline int 15 | quad_ufunc_promoter(PyUFuncObject *ufunc, PyArray_DTypeMeta *op_dtypes[], 16 | PyArray_DTypeMeta *signature[], PyArray_DTypeMeta *new_op_dtypes[]) 17 | { 18 | int nin = ufunc->nin; 19 | int nargs = ufunc->nargs; 20 | PyArray_DTypeMeta *common = NULL; 21 | bool has_quad = false; 22 | 23 | // Handle the special case for reductions 24 | if (op_dtypes[0] == NULL) { 25 | assert(nin == 2 && ufunc->nout == 1); /* must be reduction */ 26 | for (int i = 0; i < 3; i++) { 27 | Py_INCREF(op_dtypes[1]); 28 | new_op_dtypes[i] = op_dtypes[1]; 29 | } 30 | return 0; 31 | } 32 | 33 | // Check if any input or signature is QuadPrecision 34 | for (int i = 0; i < nin; i++) { 35 | if (op_dtypes[i] == &QuadPrecDType) { 36 | has_quad = true; 37 | } 38 | } 39 | 40 | if (has_quad) { 41 | common = &QuadPrecDType; 42 | } 43 | else { 44 | for (int i = nin; i < nargs; i++) { 45 | if (signature[i] != NULL) { 46 | if (common == NULL) { 47 | Py_INCREF(signature[i]); 48 | common = signature[i]; 49 | } 50 | else if (common != signature[i]) { 51 | Py_CLEAR(common); // Not homogeneous, unset common 52 | break; 53 | } 54 | } 55 | } 56 | } 57 | // If no common output dtype, use standard promotion for inputs 58 | if (common == NULL) { 59 | common = PyArray_PromoteDTypeSequence(nin, op_dtypes); 60 | if (common == NULL) { 61 | if (PyErr_ExceptionMatches(PyExc_TypeError)) { 62 | PyErr_Clear(); // Do not propagate normal promotion errors 63 | } 64 | 65 | return -1; 66 | } 67 | } 68 | 69 | // Set all new_op_dtypes to the common dtype 70 | for (int i = 0; i < nargs; i++) { 71 | if (signature[i]) { 72 | // If signature is specified for this argument, use it 73 | Py_INCREF(signature[i]); 74 | new_op_dtypes[i] = signature[i]; 75 | } 76 | else { 77 | // Otherwise, use the common dtype 78 | Py_INCREF(common); 79 | 80 | new_op_dtypes[i] = common; 81 | } 82 | } 83 | 84 | Py_XDECREF(common); 85 | 86 | return 0; 87 | } 88 | 89 | 90 | #endif -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Numpy User DTypes CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v6 16 | - name: Setup Python 17 | uses: actions/setup-python@v6 18 | with: 19 | python-version: "3.11" 20 | - name: Install build and test dependencies 21 | run: | 22 | python -m pip install -U pip build pytest unyt wheel meson ninja meson-python patchelf pandas 23 | python -m pip install --pre --upgrade --timeout=60 --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple numpy 24 | - name: Install asciidtype 25 | working-directory: asciidtype 26 | run: | 27 | CFLAGS="-Werror" python -m pip install . --no-build-isolation 28 | - name: Run asciidtype tests 29 | working-directory: asciidtype 30 | run: | 31 | pytest -vvv --color=yes 32 | - name: Install metadatadtype 33 | working-directory: metadatadtype 34 | run: | 35 | python -m build --no-isolation --wheel -Cbuilddir=build 36 | find ./dist/*.whl | xargs python -m pip install 37 | - name: Run metadatadtype tests 38 | working-directory: metadatadtype 39 | run: | 40 | pytest -vvv --color=yes 41 | - name: install mpfdtype 42 | working-directory: mpfdtype 43 | run: | 44 | sudo apt install libmpfr-dev -y 45 | CFLAGS="-Werror" python -m pip install . --no-build-isolation 46 | - name: Run mpfdtype tests 47 | working-directory: mpfdtype 48 | run: | 49 | pytest -vvv --color=yes 50 | - name: Install unytdtype 51 | working-directory: unytdtype 52 | run: | 53 | python -m build --no-isolation --wheel -Cbuilddir=build 54 | find ./dist/*.whl | xargs python -m pip install 55 | - name: Run unytdtype tests 56 | working-directory: unytdtype 57 | run: | 58 | pytest -vvv --color=yes 59 | - name: Install quaddtype dependencies 60 | run: | 61 | sudo apt-get update 62 | sudo apt-get install -y libmpfr-dev libssl-dev libfftw3-dev 63 | 64 | - name: Install quaddtype 65 | working-directory: quaddtype 66 | run: | 67 | export LDFLAGS="-fopenmp" 68 | python -m pip install .[test] -v --no-build-isolation 69 | 70 | - name: Run quaddtype tests 71 | working-directory: quaddtype 72 | run: | 73 | pytest -vvv --color=yes 74 | - name: Install stringdtype 75 | working-directory: stringdtype 76 | run: | 77 | if [ -d "build/" ] 78 | then 79 | rm -r build 80 | fi 81 | meson setup build 82 | python -m build --no-isolation --wheel -Cbuilddir=build --config-setting='compile-args=-v' -Csetup-args="-Dbuildtype=debug" 83 | find ./dist/*.whl | xargs python -m pip install 84 | - name: Run stringdtype tests 85 | working-directory: stringdtype 86 | run: | 87 | pytest -s -vvv --color=yes 88 | pip uninstall -y pandas 89 | pytest -s -vvv --color=yes 90 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/tests/test_scalar.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import sys 4 | import numpy as np 5 | import operator 6 | 7 | from mpfdtype import MPFDType, MPFloat 8 | 9 | 10 | def test_create_scalar_simple(): 11 | # currently inferring 53bit precision from float: 12 | assert MPFloat(12.).prec == 53 13 | # currently infers 64bit or 32bit depending on system: 14 | assert MPFloat(1).prec == sys.maxsize.bit_count() + 1 15 | 16 | assert MPFloat(MPFloat(12.)).prec == 53 17 | assert MPFloat(MPFloat(1)).prec == sys.maxsize.bit_count() + 1 18 | 19 | 20 | def test_create_scalar_prec(): 21 | assert MPFloat(1, prec=100).prec == 100 22 | assert MPFloat(12., prec=123).prec == 123 23 | assert MPFloat("12.234", prec=1000).prec == 1000 24 | 25 | mpf1 = MPFloat("12.4325", prec=120) 26 | mpf2 = MPFloat(mpf1, prec=150) 27 | assert mpf1 == mpf2 28 | assert mpf2.prec == 150 29 | 30 | 31 | def test_basic_equality(): 32 | assert MPFloat(12) == MPFloat(12.) == MPFloat("12.00", prec=10) 33 | 34 | 35 | @pytest.mark.parametrize("val", [123532.543, 12893283.5]) 36 | def test_scalar_repr(val): 37 | # For non exponentials at least, the repr matches: 38 | val_repr = f"{val:e}".upper() 39 | expected = f"MPFloat('{val_repr}', prec=20)" 40 | assert repr(MPFloat(val, prec=20)) == expected 41 | 42 | @pytest.mark.parametrize("op", 43 | ["add", "sub", "mul", "pow"]) 44 | @pytest.mark.parametrize("other", [3., 12.5, 100., np.nan, np.inf]) 45 | def test_binary_ops(op, other): 46 | # Generally, the math ops should behave the same as double math if they 47 | # use double precision (which they currently do). 48 | # (double could have errors, but not for these simple ops) 49 | op = getattr(operator, op) 50 | try: 51 | expected = op(12.5, other) 52 | except Exception as e: 53 | with pytest.raises(type(e)): 54 | op(MPFloat(12.5), other) 55 | with pytest.raises(type(e)): 56 | op(12.5, MPFloat(other)) 57 | with pytest.raises(type(e)): 58 | op(MPFloat(12.5), MPFloat(other)) 59 | else: 60 | if np.isnan(expected): 61 | # Avoiding isnan (which was also not implemented when written) 62 | res = op(MPFloat(12.5), other) 63 | assert res != res 64 | res = op(12.5, MPFloat(other)) 65 | assert res != res 66 | res = op(MPFloat(12.5), MPFloat(other)) 67 | assert res != res 68 | else: 69 | assert op(MPFloat(12.5), other) == expected 70 | assert op(12.5, MPFloat(other)) == expected 71 | assert op(MPFloat(12.5), MPFloat(other)) == expected 72 | 73 | 74 | @pytest.mark.parametrize("op", 75 | ["eq", "ne", "le", "lt", "ge", "gt"]) 76 | @pytest.mark.parametrize("other", [3., 12.5, 100., np.nan, np.inf]) 77 | def test_comparisons(op, other): 78 | op = getattr(operator, op) 79 | expected = op(12.5, other) 80 | assert op(MPFloat(12.5), other) is expected 81 | assert op(12.5, MPFloat(other)) is expected 82 | assert op(MPFloat(12.5), MPFloat(other)) is expected 83 | 84 | 85 | @pytest.mark.parametrize("op", 86 | ["neg", "pos", "abs"]) 87 | @pytest.mark.parametrize("val", [3., 12.5, 100., np.nan, np.inf]) 88 | def test_comparisons(op, val): 89 | op = getattr(operator, op) 90 | expected = op(val) 91 | if np.isnan(expected): 92 | assert op(MPFloat(val)) != op(MPFloat(val)) 93 | else: 94 | assert op(MPFloat(val)) == expected 95 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/umath.cpp: -------------------------------------------------------------------------------- 1 | #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API 2 | #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API 3 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 4 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 5 | #define NO_IMPORT_ARRAY 6 | #define NO_IMPORT_UFUNC 7 | 8 | extern "C" { 9 | #include 10 | #include 11 | 12 | #include "numpy/arrayobject.h" 13 | #include "numpy/ndarraytypes.h" 14 | #include "numpy/ufuncobject.h" 15 | #include "numpy/dtype_api.h" 16 | } 17 | #include "../quad_common.h" 18 | #include "../scalar.h" 19 | #include "../dtype.h" 20 | #include "umath.h" 21 | #include "../ops.hpp" 22 | #include "unary_ops.h" 23 | #include "unary_props.h" 24 | #include "binary_ops.h" 25 | #include "comparison_ops.h" 26 | #include "matmul.h" 27 | 28 | // helper debugging function 29 | static const char * 30 | get_dtype_name(PyArray_DTypeMeta *dtype) 31 | { 32 | if (dtype == &QuadPrecDType) { 33 | return "QuadPrecDType"; 34 | } 35 | else if (dtype == &PyArray_BoolDType) { 36 | return "BoolDType"; 37 | } 38 | else if (dtype == &PyArray_ByteDType) { 39 | return "ByteDType"; 40 | } 41 | else if (dtype == &PyArray_UByteDType) { 42 | return "UByteDType"; 43 | } 44 | else if (dtype == &PyArray_ShortDType) { 45 | return "ShortDType"; 46 | } 47 | else if (dtype == &PyArray_UShortDType) { 48 | return "UShortDType"; 49 | } 50 | else if (dtype == &PyArray_IntDType) { 51 | return "IntDType"; 52 | } 53 | else if (dtype == &PyArray_UIntDType) { 54 | return "UIntDType"; 55 | } 56 | else if (dtype == &PyArray_LongDType) { 57 | return "LongDType"; 58 | } 59 | else if (dtype == &PyArray_ULongDType) { 60 | return "ULongDType"; 61 | } 62 | else if (dtype == &PyArray_LongLongDType) { 63 | return "LongLongDType"; 64 | } 65 | else if (dtype == &PyArray_ULongLongDType) { 66 | return "ULongLongDType"; 67 | } 68 | else if (dtype == &PyArray_FloatDType) { 69 | return "FloatDType"; 70 | } 71 | else if (dtype == &PyArray_DoubleDType) { 72 | return "DoubleDType"; 73 | } 74 | else if (dtype == &PyArray_LongDoubleDType) { 75 | return "LongDoubleDType"; 76 | } 77 | else { 78 | return "UnknownDType"; 79 | } 80 | } 81 | 82 | int 83 | init_quad_umath(void) 84 | { 85 | PyObject *numpy = PyImport_ImportModule("numpy"); 86 | if (!numpy) { 87 | PyErr_SetString(PyExc_ImportError, "Failed to import numpy module"); 88 | return -1; 89 | } 90 | 91 | if (init_quad_unary_ops(numpy) < 0) { 92 | PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad unary operations"); 93 | goto err; 94 | } 95 | 96 | if (init_quad_unary_props(numpy) < 0) { 97 | PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad unary properties"); 98 | goto err; 99 | } 100 | 101 | if (init_quad_binary_ops(numpy) < 0) { 102 | PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad binary operations"); 103 | goto err; 104 | } 105 | 106 | if (init_quad_comps(numpy) < 0) { 107 | PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad comparison operations"); 108 | goto err; 109 | } 110 | 111 | if (init_matmul_ops(numpy) < 0) { 112 | PyErr_SetString(PyExc_RuntimeError, "Failed to initialize quad matrix multiplication operations"); 113 | goto err; 114 | } 115 | 116 | Py_DECREF(numpy); 117 | return 0; 118 | 119 | err: 120 | Py_DECREF(numpy); 121 | return -1; 122 | } -------------------------------------------------------------------------------- /unytdtype/unytdtype/src/umath.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL unytdtype_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL unytdtype_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 7 | #define NO_IMPORT_ARRAY 8 | #define NO_IMPORT_UFUNC 9 | #include "numpy/arrayobject.h" 10 | #include "numpy/dtype_api.h" 11 | #include "numpy/ndarraytypes.h" 12 | #include "numpy/ufuncobject.h" 13 | 14 | #include "dtype.h" 15 | #include "umath.h" 16 | 17 | static int 18 | unit_multiply_strided_loop(PyArrayMethod_Context *context, char *const data[], 19 | npy_intp const dimensions[], 20 | npy_intp const strides[], NpyAuxData *auxdata) 21 | { 22 | npy_intp N = dimensions[0]; 23 | char *in1 = data[0], *in2 = data[1]; 24 | char *out = data[2]; 25 | npy_intp in1_stride = strides[0]; 26 | npy_intp in2_stride = strides[1]; 27 | npy_intp out_stride = strides[2]; 28 | 29 | while (N--) { 30 | *(double *)out = *(double *)in1 * *(double *)in2; 31 | in1 += in1_stride; 32 | in2 += in2_stride; 33 | out += out_stride; 34 | } 35 | return 0; 36 | } 37 | 38 | static NPY_CASTING 39 | unit_multiply_resolve_descriptors(PyObject *self, PyArray_DTypeMeta *dtypes[], 40 | PyArray_Descr *given_descrs[], 41 | PyArray_Descr *loop_descrs[], 42 | npy_intp *unused) 43 | { 44 | /* Fetch the unyt based units: */ 45 | PyObject *unit1 = ((UnytDTypeObject *)given_descrs[0])->unit; 46 | PyObject *unit2 = ((UnytDTypeObject *)given_descrs[1])->unit; 47 | /* Find the correct result unit: */ 48 | PyObject *new_unit = PyNumber_Multiply(unit1, unit2); 49 | if (new_unit == NULL) { 50 | return -1; 51 | } 52 | 53 | /* Create new DType from the new unit: */ 54 | loop_descrs[2] = (PyArray_Descr *)new_unytdtype_instance(new_unit); 55 | if (loop_descrs[2] == NULL) { 56 | return -1; 57 | } 58 | /* The other operand units can be used as-is: */ 59 | Py_INCREF(given_descrs[0]); 60 | loop_descrs[0] = given_descrs[0]; 61 | Py_INCREF(given_descrs[1]); 62 | loop_descrs[1] = given_descrs[1]; 63 | 64 | return NPY_NO_CASTING; 65 | } 66 | 67 | /* 68 | * Function that adds our multiply loop to NumPy's multiply ufunc. 69 | */ 70 | int 71 | init_multiply_ufunc(void) 72 | { 73 | /* 74 | * Get the multiply ufunc: 75 | */ 76 | PyObject *numpy = PyImport_ImportModule("numpy"); 77 | if (numpy == NULL) { 78 | return -1; 79 | } 80 | PyObject *multiply = PyObject_GetAttrString(numpy, "multiply"); 81 | Py_DECREF(numpy); 82 | if (multiply == NULL) { 83 | return -1; 84 | } 85 | 86 | /* 87 | * The initializing "wrap up" code from the slides (plus one error check) 88 | */ 89 | static PyArray_DTypeMeta *dtypes[3] = {&UnytDType, &UnytDType, &UnytDType}; 90 | 91 | static PyType_Slot slots[] = { 92 | {NPY_METH_resolve_descriptors, &unit_multiply_resolve_descriptors}, 93 | {NPY_METH_strided_loop, &unit_multiply_strided_loop}, 94 | {0, NULL}}; 95 | 96 | PyArrayMethod_Spec MultiplySpec = { 97 | .name = "unyt_multiply", 98 | .nin = 2, 99 | .nout = 1, 100 | .dtypes = dtypes, 101 | .slots = slots, 102 | .flags = 0, 103 | .casting = NPY_NO_CASTING, 104 | }; 105 | 106 | /* Register */ 107 | if (PyUFunc_AddLoopFromSpec(multiply, &MultiplySpec) < 0) { 108 | Py_DECREF(multiply); 109 | return -1; 110 | } 111 | Py_DECREF(multiply); 112 | return 0; 113 | } 114 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/quaddtype_main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API 7 | #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API 8 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 9 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 10 | 11 | #include "numpy/arrayobject.h" 12 | #include "numpy/dtype_api.h" 13 | #include "numpy/ufuncobject.h" 14 | 15 | #include "lock.h" 16 | #include "scalar.h" 17 | #include "dtype.h" 18 | #include "umath/umath.h" 19 | #include "quad_common.h" 20 | #include "quadblas_interface.h" 21 | #include "constants.hpp" 22 | #include "float.h" 23 | 24 | static PyObject * 25 | py_is_longdouble_128(PyObject *self, PyObject *args) 26 | { 27 | if (sizeof(long double) == 16 && LDBL_MANT_DIG == 113 && LDBL_MAX_EXP == 16384) { 28 | Py_RETURN_TRUE; 29 | } 30 | else { 31 | Py_RETURN_FALSE; 32 | } 33 | } 34 | 35 | static PyObject * 36 | get_sleef_constant(PyObject *self, PyObject *args) 37 | { 38 | const char *constant_name; 39 | if (!PyArg_ParseTuple(args, "s", &constant_name)) { 40 | return NULL; 41 | } 42 | 43 | ConstantResult const_result = get_sleef_constant_by_name(constant_name); 44 | 45 | if (const_result.type == CONSTANT_ERROR) { 46 | PyErr_SetString(PyExc_ValueError, "Unknown constant name"); 47 | return NULL; 48 | } 49 | 50 | if (const_result.type == CONSTANT_INT64) { 51 | return PyLong_FromLongLong(const_result.data.int_value); 52 | } 53 | 54 | if (const_result.type == CONSTANT_QUAD) { 55 | QuadPrecisionObject *result = QuadPrecision_raw_new(BACKEND_SLEEF); 56 | if (result == NULL) { 57 | return NULL; 58 | } 59 | result->value.sleef_value = const_result.data.quad_value; 60 | return (PyObject *)result; 61 | } 62 | 63 | // Should never reach here 64 | PyErr_SetString(PyExc_RuntimeError, "Unexpected constant result type"); 65 | return NULL; 66 | } 67 | 68 | static PyMethodDef module_methods[] = { 69 | {"is_longdouble_128", py_is_longdouble_128, METH_NOARGS, "Check if long double is 128-bit"}, 70 | {"get_sleef_constant", get_sleef_constant, METH_VARARGS, "Get Sleef constant by name"}, 71 | {"set_num_threads", py_quadblas_set_num_threads, METH_VARARGS, 72 | "Set number of threads for QuadBLAS"}, 73 | {"get_num_threads", py_quadblas_get_num_threads, METH_NOARGS, 74 | "Get number of threads for QuadBLAS"}, 75 | {"get_quadblas_version", py_quadblas_get_version, METH_NOARGS, "Get QuadBLAS version"}, 76 | {NULL, NULL, 0, NULL}}; 77 | 78 | static struct PyModuleDef moduledef = { 79 | PyModuleDef_HEAD_INIT, 80 | .m_name = "_quaddtype_main", 81 | .m_doc = "Quad (128-bit) floating point Data Type for NumPy with multiple backends", 82 | .m_size = -1, 83 | .m_methods = module_methods, 84 | }; 85 | 86 | PyMODINIT_FUNC 87 | PyInit__quaddtype_main(void) 88 | { 89 | import_array(); 90 | import_umath(); 91 | PyObject *m = PyModule_Create(&moduledef); 92 | if (!m) { 93 | return NULL; 94 | } 95 | 96 | #ifdef Py_GIL_DISABLED 97 | PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); 98 | #endif 99 | 100 | init_sleef_locks(); 101 | 102 | if (init_quadprecision_scalar() < 0) 103 | goto error; 104 | 105 | if (PyModule_AddObject(m, "QuadPrecision", (PyObject *)&QuadPrecision_Type) < 0) 106 | goto error; 107 | 108 | if (init_quadprec_dtype() < 0) 109 | goto error; 110 | 111 | if (PyModule_AddObject(m, "QuadPrecDType", (PyObject *)&QuadPrecDType) < 0) 112 | goto error; 113 | 114 | if (init_quad_umath() < 0) { 115 | goto error; 116 | } 117 | 118 | return m; 119 | 120 | error: 121 | Py_XDECREF(m); 122 | return NULL; 123 | } -------------------------------------------------------------------------------- /quaddtype/tests/utils.py: -------------------------------------------------------------------------------- 1 | from numpy_quaddtype import QuadPrecision, QuadPrecDType 2 | import numpy as np 3 | 4 | def assert_quad_equal(a, b, rtol=1e-15, atol=1e-15): 5 | """Assert two quad precision values are equal within tolerance""" 6 | # Ensure both operands are QuadPrecision objects for the comparison 7 | if not isinstance(a, QuadPrecision): 8 | a = QuadPrecision(str(a), backend='sleef') 9 | if not isinstance(b, QuadPrecision): 10 | b = QuadPrecision(str(b), backend='sleef') 11 | 12 | # Use quad-precision arithmetic to calculate the difference 13 | diff = abs(a - b) 14 | tolerance = QuadPrecision(str(atol), backend='sleef') + QuadPrecision(str(rtol), backend='sleef') * max(abs(a), abs(b)) 15 | 16 | # Assert using quad-precision objects 17 | assert diff <= tolerance, f"Values not equal: {a} != {b} (diff: {diff}, tol: {tolerance})" 18 | 19 | 20 | def assert_quad_array_equal(a, b, rtol=1e-25, atol=1e-25): 21 | """Assert two quad precision arrays are equal within tolerance""" 22 | assert a.shape == b.shape, f"Shapes don't match: {a.shape} vs {b.shape}" 23 | 24 | flat_a = a.flatten() 25 | flat_b = b.flatten() 26 | 27 | for i, (val_a, val_b) in enumerate(zip(flat_a, flat_b)): 28 | try: 29 | assert_quad_equal(val_a, val_b, rtol, atol) 30 | except AssertionError as e: 31 | raise AssertionError(f"Arrays differ at index {i}: {e}") 32 | 33 | 34 | def create_quad_array(values, shape=None): 35 | """Create a QuadPrecision array from values using Sleef backend""" 36 | dtype = QuadPrecDType(backend='sleef') 37 | 38 | if isinstance(values, (list, tuple)): 39 | if shape is None: 40 | # 1D array 41 | quad_values = [QuadPrecision(str(float(v)), backend='sleef') for v in values] 42 | return np.array(quad_values, dtype=dtype) 43 | else: 44 | # Reshape to specified shape 45 | if len(shape) == 1: 46 | quad_values = [QuadPrecision(str(float(v)), backend='sleef') for v in values] 47 | return np.array(quad_values, dtype=dtype) 48 | elif len(shape) == 2: 49 | m, n = shape 50 | assert len(values) == m * n, f"Values length {len(values)} doesn't match shape {shape}" 51 | quad_matrix = [] 52 | for i in range(m): 53 | row = [QuadPrecision(str(float(values[i * n + j])), backend='sleef') for j in range(n)] 54 | quad_matrix.append(row) 55 | return np.array(quad_matrix, dtype=dtype) 56 | 57 | raise ValueError("Unsupported values or shape") 58 | 59 | 60 | def is_special_value(val): 61 | """Check if a value is NaN or infinite""" 62 | try: 63 | float_val = float(val) 64 | return np.isnan(float_val) or np.isinf(float_val) 65 | except: 66 | return False 67 | 68 | 69 | def arrays_equal_with_nan(a, b, rtol=1e-15, atol=1e-15): 70 | """Compare arrays that may contain NaN values""" 71 | if a.shape != b.shape: 72 | return False 73 | 74 | flat_a = a.flatten() 75 | flat_b = b.flatten() 76 | 77 | for i, (val_a, val_b) in enumerate(zip(flat_a, flat_b)): 78 | # Handle NaN cases 79 | if is_special_value(val_a) and is_special_value(val_b): 80 | float_a = float(val_a) 81 | float_b = float(val_b) 82 | # Both NaN 83 | if np.isnan(float_a) and np.isnan(float_b): 84 | continue 85 | # Both infinite with same sign 86 | elif np.isinf(float_a) and np.isinf(float_b) and np.sign(float_a) == np.sign(float_b): 87 | continue 88 | else: 89 | return False 90 | elif is_special_value(val_a) or is_special_value(val_b): 91 | return False 92 | else: 93 | try: 94 | assert_quad_equal(val_a, val_b, rtol, atol) 95 | except AssertionError: 96 | return False 97 | 98 | return True -------------------------------------------------------------------------------- /stringdtype/stringdtype/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL stringdtype_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL stringdtype_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 7 | #include "numpy/ndarraytypes.h" 8 | #include "numpy/arrayobject.h" 9 | #include "numpy/ufuncobject.h" 10 | #include "numpy/dtype_api.h" 11 | 12 | #include "dtype.h" 13 | #include "static_string.h" 14 | #include "umath.h" 15 | 16 | static PyObject * 17 | _memory_usage(PyObject *NPY_UNUSED(self), PyObject *obj) 18 | { 19 | if (!PyArray_Check(obj)) { 20 | PyErr_SetString(PyExc_TypeError, 21 | "can only be called with ndarray object"); 22 | return NULL; 23 | } 24 | 25 | PyArrayObject *arr = (PyArrayObject *)obj; 26 | 27 | PyArray_Descr *descr = PyArray_DESCR(arr); 28 | PyArray_DTypeMeta *dtype = NPY_DTYPE(descr); 29 | 30 | if (dtype != (PyArray_DTypeMeta *)&StringDType) { 31 | PyErr_SetString(PyExc_TypeError, 32 | "can only be called with a StringDType array"); 33 | return NULL; 34 | } 35 | 36 | NpyIter *iter = NpyIter_New( 37 | arr, NPY_ITER_READONLY | NPY_ITER_EXTERNAL_LOOP | NPY_ITER_REFS_OK, 38 | NPY_KEEPORDER, NPY_NO_CASTING, NULL); 39 | 40 | if (iter == NULL) { 41 | return NULL; 42 | } 43 | 44 | NpyIter_IterNextFunc *iternext = NpyIter_GetIterNext(iter, NULL); 45 | 46 | if (iternext == NULL) { 47 | NpyIter_Deallocate(iter); 48 | return NULL; 49 | } 50 | 51 | char **dataptr = NpyIter_GetDataPtrArray(iter); 52 | npy_intp *strideptr = NpyIter_GetInnerStrideArray(iter); 53 | npy_intp *innersizeptr = NpyIter_GetInnerLoopSizePtr(iter); 54 | 55 | // initialize with the size of the internal buffer 56 | size_t memory_usage = PyArray_NBYTES(arr); 57 | 58 | do { 59 | char *in = dataptr[0]; 60 | npy_intp stride = *strideptr; 61 | npy_intp count = *innersizeptr; 62 | 63 | while (count--) { 64 | size_t size = _NpyString_size(((npy_packed_static_string *)in)); 65 | // FIXME: add a way for a string to report its heap size usage 66 | if (size > (sizeof(_npy_static_string) - 1)) { 67 | memory_usage += size; 68 | } 69 | in += stride; 70 | } 71 | 72 | } while (iternext(iter)); 73 | 74 | NpyIter_Deallocate(iter); 75 | 76 | PyObject *ret = PyLong_FromSize_t(memory_usage); 77 | 78 | return ret; 79 | } 80 | 81 | static PyMethodDef string_methods[] = { 82 | {"_memory_usage", _memory_usage, METH_O, 83 | "get memory usage for an array"}, 84 | {NULL, NULL, 0, NULL}, 85 | }; 86 | 87 | static struct PyModuleDef moduledef = { 88 | PyModuleDef_HEAD_INIT, 89 | .m_name = "stringdtype_main", 90 | .m_size = -1, 91 | .m_methods = string_methods, 92 | }; 93 | 94 | /* Module initialization function */ 95 | PyMODINIT_FUNC 96 | PyInit__main(void) 97 | { 98 | import_array(); 99 | import_umath(); 100 | 101 | PyObject *m = PyModule_Create(&moduledef); 102 | if (m == NULL) { 103 | return NULL; 104 | } 105 | 106 | PyObject *mod = PyImport_ImportModule("stringdtype"); 107 | if (mod == NULL) { 108 | goto error; 109 | } 110 | 111 | StringScalar_Type = 112 | (PyTypeObject *)PyObject_GetAttrString(mod, "StringScalar"); 113 | 114 | if (StringScalar_Type == NULL) { 115 | goto error; 116 | } 117 | 118 | Py_DECREF(mod); 119 | 120 | if (init_string_dtype() < 0) { 121 | goto error; 122 | } 123 | 124 | Py_INCREF((PyObject *)&StringDType); 125 | if (PyModule_AddObject(m, "StringDType", (PyObject *)&StringDType) < 0) { 126 | Py_DECREF((PyObject *)&StringDType); 127 | goto error; 128 | } 129 | 130 | if (init_ufuncs() == -1) { 131 | goto error; 132 | } 133 | 134 | return m; 135 | 136 | error: 137 | Py_DECREF(m); 138 | return NULL; 139 | } 140 | -------------------------------------------------------------------------------- /metadatadtype/metadatadtype/src/umath.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL metadatadtype_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL metadatadtype_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 7 | #define NO_IMPORT_ARRAY 8 | #define NO_IMPORT_UFUNC 9 | #include "numpy/ndarraytypes.h" 10 | #include "numpy/arrayobject.h" 11 | #include "numpy/ufuncobject.h" 12 | #include "numpy/dtype_api.h" 13 | 14 | #include "dtype.h" 15 | #include "umath.h" 16 | 17 | static int 18 | translate_given_descrs(int nin, int nout, 19 | PyArray_DTypeMeta *const NPY_UNUSED(wrapped_dtypes[]), 20 | PyArray_Descr *const given_descrs[], 21 | PyArray_Descr *new_descrs[]) 22 | { 23 | for (int i = 0; i < nin + nout; i++) { 24 | if (given_descrs[i] == NULL) { 25 | new_descrs[i] = NULL; 26 | } 27 | else { 28 | if (NPY_DTYPE(given_descrs[i]) == &PyArray_BoolDType) { 29 | new_descrs[i] = PyArray_DescrFromType(NPY_BOOL); 30 | } 31 | else { 32 | new_descrs[i] = PyArray_DescrFromType(NPY_DOUBLE); 33 | } 34 | } 35 | } 36 | return 0; 37 | } 38 | 39 | static int 40 | translate_loop_descrs(int nin, int NPY_UNUSED(nout), 41 | PyArray_DTypeMeta *const NPY_UNUSED(new_dtypes[]), 42 | PyArray_Descr *const given_descrs[], 43 | PyArray_Descr *original_descrs[], 44 | PyArray_Descr *loop_descrs[]) 45 | { 46 | if (nin == 2) { 47 | loop_descrs[0] = 48 | common_instance((MetadataDTypeObject *)given_descrs[0], 49 | (MetadataDTypeObject *)given_descrs[1]); 50 | if (loop_descrs[0] == NULL) { 51 | return -1; 52 | } 53 | Py_INCREF(loop_descrs[0]); 54 | loop_descrs[1] = loop_descrs[0]; 55 | Py_INCREF(loop_descrs[1]); 56 | if (NPY_DTYPE(original_descrs[2]) == &PyArray_BoolDType) { 57 | loop_descrs[2] = PyArray_DescrFromType(NPY_BOOL); 58 | } 59 | else { 60 | loop_descrs[2] = loop_descrs[0]; 61 | } 62 | Py_INCREF(loop_descrs[2]); 63 | } 64 | else if (nin == 1) { 65 | loop_descrs[0] = given_descrs[0]; 66 | Py_INCREF(loop_descrs[0]); 67 | if (NPY_DTYPE(original_descrs[1]) == &PyArray_BoolDType) { 68 | loop_descrs[1] = PyArray_DescrFromType(NPY_BOOL); 69 | } 70 | else { 71 | loop_descrs[1] = loop_descrs[0]; 72 | } 73 | Py_INCREF(loop_descrs[1]); 74 | } 75 | else { 76 | return -1; 77 | } 78 | return 0; 79 | } 80 | 81 | static PyObject * 82 | get_ufunc(const char *ufunc_name) 83 | { 84 | PyObject *mod = PyImport_ImportModule("numpy"); 85 | if (mod == NULL) { 86 | return NULL; 87 | } 88 | PyObject *ufunc = PyObject_GetAttrString(mod, ufunc_name); 89 | Py_DECREF(mod); 90 | 91 | return ufunc; 92 | } 93 | 94 | static int 95 | add_wrapping_loop(const char *ufunc_name, PyArray_DTypeMeta **dtypes, 96 | PyArray_DTypeMeta **wrapped_dtypes) 97 | { 98 | PyObject *ufunc = get_ufunc(ufunc_name); 99 | if (ufunc == NULL) { 100 | return -1; 101 | } 102 | int res = PyUFunc_AddWrappingLoop(ufunc, dtypes, wrapped_dtypes, 103 | &translate_given_descrs, 104 | &translate_loop_descrs); 105 | return res; 106 | } 107 | 108 | int 109 | init_ufuncs(void) 110 | { 111 | PyArray_DTypeMeta *binary_orig_dtypes[3] = {&MetadataDType, &MetadataDType, 112 | &MetadataDType}; 113 | PyArray_DTypeMeta *binary_wrapped_dtypes[3] = { 114 | &PyArray_DoubleDType, &PyArray_DoubleDType, &PyArray_DoubleDType}; 115 | if (add_wrapping_loop("multiply", binary_orig_dtypes, 116 | binary_wrapped_dtypes) == -1) { 117 | goto error; 118 | } 119 | 120 | PyArray_DTypeMeta *unary_boolean_dtypes[2] = {&MetadataDType, 121 | &PyArray_BoolDType}; 122 | PyArray_DTypeMeta *unary_boolean_wrapped_dtypes[2] = {&PyArray_DoubleDType, 123 | &PyArray_BoolDType}; 124 | if (add_wrapping_loop("isnan", unary_boolean_dtypes, 125 | unary_boolean_wrapped_dtypes) == -1) { 126 | goto error; 127 | } 128 | 129 | return 0; 130 | error: 131 | 132 | return -1; 133 | } 134 | -------------------------------------------------------------------------------- /quaddtype/tests/test_finfo.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import numpy as np 3 | from utils import assert_quad_equal as assert_quad_close 4 | from numpy_quaddtype import QuadPrecDType, QuadPrecision 5 | 6 | 7 | class TestFinfoConstants: 8 | """Test suite for verifying all finfo constants are correctly implemented.""" 9 | 10 | def test_basic_integer_properties(self): 11 | """Test basic integer properties of finfo.""" 12 | dtype = QuadPrecDType() 13 | finfo = np.finfo(dtype) 14 | 15 | # Test basic properties 16 | assert finfo.bits == 128, f"Expected bits=128, got {finfo.bits}" 17 | assert finfo.nmant == 112, f"Expected nmant=112 (mantissa bits), got {finfo.nmant}" 18 | assert finfo.nexp == 15, f"Expected nexp=15 (exponent bits including sign and bias), got {finfo.nexp}" 19 | assert finfo.iexp == 15, f"Expected iexp=15 (exponent bits), got {finfo.iexp}" 20 | assert finfo.minexp == -16382, f"Expected minexp=-16382, got {finfo.minexp}" 21 | assert finfo.maxexp == 16384, f"Expected maxexp=16384, got {finfo.maxexp}" 22 | assert finfo.precision == 33, f"Expected precision=33 (decimal digits), got {finfo.precision}" 23 | assert finfo.machep == -112, f"Expected machep=-112, got {finfo.machep}" 24 | assert finfo.negep == -113, f"Expected negep=-113, got {finfo.negep}" 25 | 26 | def test_epsilon_values(self): 27 | """Test epsilon values (eps and epsneg).""" 28 | dtype = QuadPrecDType() 29 | finfo = np.finfo(dtype) 30 | 31 | # eps = 2^-112 (difference between 1.0 and next larger representable float) 32 | assert isinstance(finfo.eps, QuadPrecision), f"eps should be QuadPrecision, got {type(finfo.eps)}" 33 | assert_quad_close(finfo.eps, "1.925929944387235853055977942584927e-034") 34 | 35 | # epsneg = 2^-113 (difference between 1.0 and next smaller representable float) 36 | assert isinstance(finfo.epsneg, QuadPrecision), f"epsneg should be QuadPrecision, got {type(finfo.epsneg)}" 37 | assert_quad_close(finfo.epsneg, "9.629649721936179265279889712924637e-035") 38 | 39 | def test_max_and_min_values(self): 40 | """Test maximum and minimum finite values.""" 41 | dtype = QuadPrecDType() 42 | finfo = np.finfo(dtype) 43 | 44 | # max = SLEEF_QUAD_MAX 45 | assert isinstance(finfo.max, QuadPrecision), f"max should be QuadPrecision, got {type(finfo.max)}" 46 | assert_quad_close(finfo.max, "1.189731495357231765085759326628007e+4932") 47 | 48 | # min = -SLEEF_QUAD_MAX (most negative finite value) 49 | assert isinstance(finfo.min, QuadPrecision), f"min should be QuadPrecision, got {type(finfo.min)}" 50 | assert_quad_close(finfo.min, "-1.189731495357231765085759326628007e+4932") 51 | 52 | # Verify min is negative 53 | zero = QuadPrecision("0") 54 | assert finfo.min < zero, f"min should be negative, got {finfo.min}" 55 | 56 | def test_tiny_and_smallest_values(self): 57 | """Test tiny (smallest_normal) and smallest_subnormal values.""" 58 | dtype = QuadPrecDType() 59 | finfo = np.finfo(dtype) 60 | 61 | # tiny = smallest_normal = SLEEF_QUAD_MIN = 2^-16382 62 | assert isinstance(finfo.tiny, QuadPrecision), f"tiny should be QuadPrecision, got {type(finfo.tiny)}" 63 | assert isinstance(finfo.smallest_normal, QuadPrecision), \ 64 | f"smallest_normal should be QuadPrecision, got {type(finfo.smallest_normal)}" 65 | 66 | # tiny and smallest_normal should be the same 67 | assert finfo.tiny == finfo.smallest_normal, \ 68 | f"tiny and smallest_normal should be equal, got {finfo.tiny} != {finfo.smallest_normal}" 69 | assert_quad_close(finfo.tiny, "3.362103143112093506262677817321753e-4932") 70 | 71 | # smallest_subnormal = 2^-16494 (smallest positive representable number) 72 | assert isinstance(finfo.smallest_subnormal, QuadPrecision), \ 73 | f"smallest_subnormal should be QuadPrecision, got {type(finfo.smallest_subnormal)}" 74 | assert_quad_close(finfo.smallest_subnormal, "6.0e-4966") 75 | 76 | def test_resolution(self): 77 | """Test resolution property (10^-precision).""" 78 | dtype = QuadPrecDType() 79 | finfo = np.finfo(dtype) 80 | 81 | # Resolution should be approximately 10^-33 for quad precision 82 | assert isinstance(finfo.resolution, QuadPrecision), \ 83 | f"resolution should be QuadPrecision, got {type(finfo.resolution)}" 84 | assert_quad_close(finfo.resolution, "1.0e-33") 85 | 86 | 87 | if __name__ == "__main__": 88 | pytest.main([__file__, "-v"]) 89 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: generate-compilation-database-metadatadtype 5 | name: Generate compilation database [metadatadtype] 6 | files: metadatadtype/(meson\.build$|.*\.(c|h)$) 7 | language: system 8 | require_serial: true 9 | entry: | 10 | bash -c 'cd metadatadtype && mkdir -p build && pip install build meson-python patchelf wheel && meson setup build && python -m build --wheel --no-isolation -Cbuilddir=build'; 11 | 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 && meson setup build && python -m build --wheel --no-isolation -Cbuilddir=build'; 19 | fail_fast: false 20 | - id: generate-compilation-database-quaddtype 21 | name: Generate compilation database [quaddtype] 22 | files: quaddtype/(meson\.build$|.*\.(c|h)$) 23 | language: system 24 | require_serial: true 25 | entry: | 26 | bash -c 'cd quaddtype && mkdir -p build && pip install build meson-python patchelf wheel && meson setup build && python -m build --wheel --no-isolation -Cbuilddir=build'; 27 | fail_fast: false 28 | - id: generate-compilation-database-unytdtype 29 | name: Generate compilation database [unytdtype] 30 | files: unytdtype/(meson\.build$|.*\.(c|h)$) 31 | language: system 32 | require_serial: true 33 | entry: | 34 | bash -c 'cd unytdtype && mkdir -p build && pip install build meson-python patchelf wheel && meson setup build && python -m build --wheel --no-isolation -Cbuilddir=build'; 35 | fail_fast: false 36 | - id: generate-compilation-database-stringdtype 37 | name: Generate compilation database [stringdtype] 38 | files: stringdtype/(meson\.build$|.*\.(c|h)$) 39 | language: system 40 | require_serial: true 41 | entry: | 42 | bash -c 'cd stringdtype && mkdir -p build && pip install build meson-python patchelf wheel && meson setup build && python -m build --wheel --no-isolation -Cbuilddir=build'; 43 | fail_fast: false 44 | - repo: https://github.com/pocc/pre-commit-hooks 45 | rev: v1.3.5 46 | hooks: 47 | - id: clang-tidy 48 | name: clang-tidy [metadatadtype] 49 | args: [-p=metadatadtype/build] 50 | files: metadatadtype/(.*\.(c|h)$) 51 | - id: clang-tidy 52 | name: clang-tidy [quaddtype] 53 | args: [-p=quaddtype/build] 54 | files: quaddtype/(.*\.(c|h)$) 55 | - id: clang-tidy 56 | name: clang-tidy [unytdtype] 57 | args: [-p=unytdtype/build] 58 | files: unytdtype/(.*\.(c|h)$) 59 | - id: clang-tidy 60 | name: clang-tidy [stringdtype] 61 | args: [-p=stringdtype/build] 62 | files: stringdtype/(.*\.(c|h)$) 63 | - id: clang-format 64 | args: ['--no-diff', -i] 65 | # - id: oclint 66 | # - id: cppcheck 67 | - repo: https://github.com/pre-commit/pre-commit-hooks 68 | rev: v4.4.0 69 | hooks: 70 | - id: trailing-whitespace 71 | - id: end-of-file-fixer 72 | - id: check-yaml 73 | - id: check-added-large-files 74 | - id: check-ast 75 | - repo: https://github.com/charliermarsh/ruff-pre-commit 76 | rev: v0.0.254 77 | hooks: 78 | - id: ruff 79 | - repo: https://github.com/pre-commit/mirrors-prettier 80 | rev: v3.0.0-alpha.6 81 | hooks: 82 | - id: prettier 83 | types: 84 | [ 85 | markdown, 86 | yaml, 87 | ] 88 | - repo: https://github.com/pycqa/isort 89 | rev: 5.12.0 90 | hooks: 91 | - id: isort 92 | name: isort (python) 93 | - id: isort 94 | name: isort (cython) 95 | types: [cython] 96 | - id: isort 97 | name: isort (pyi) 98 | types: [pyi] 99 | - repo: https://github.com/psf/black 100 | rev: 23.1.0 101 | hooks: 102 | - id: black 103 | name: 'black for asciidtype' 104 | files: '^asciidtype/.*\.py' 105 | - id: black 106 | name: 'black for metadatadtype' 107 | files: '^metadatadtype/.*\.py' 108 | - id: black 109 | name: 'black for mpfdtype' 110 | files: '^mpfdtype/.*\.py' 111 | - id: black 112 | name: 'black for quaddtype' 113 | files: '^quaddtype/.*\.py' 114 | - id: black 115 | name: 'black for stringdtype' 116 | files: '^stringdtype/.*\.py' 117 | - id: black 118 | name: 'black for unytdtype' 119 | files: '^unytdtype/.*\.py' 120 | -------------------------------------------------------------------------------- /stringdtype/stringdtype/src/dtype.h: -------------------------------------------------------------------------------- 1 | #ifndef _NPY_DTYPE_H 2 | #define _NPY_DTYPE_H 3 | 4 | // clang-format off 5 | #include 6 | #include "structmember.h" 7 | // clang-format on 8 | 9 | #include "static_string.h" 10 | 11 | // not publicly exposed by the static string library so we need to define 12 | // this here so we can define `elsize` and `alignment` on the descr 13 | // 14 | // if the layout of npy_packed_static_string ever changes in the future 15 | // this may need to be updated. 16 | #define SIZEOF_NPY_PACKED_STATIC_STRING 2 * sizeof(size_t) 17 | #define ALIGNOF_NPY_PACKED_STATIC_STRING _Alignof(size_t) 18 | 19 | typedef struct { 20 | PyArray_Descr base; 21 | PyObject *na_object; 22 | int coerce; 23 | int has_nan_na; 24 | int has_string_na; 25 | int array_owned; 26 | _npy_static_string default_string; 27 | _npy_static_string na_name; 28 | PyThread_type_lock *allocator_lock; 29 | // the allocator should only be directly accessed after 30 | // acquiring the allocator_lock and the lock should 31 | // be released immediately after the allocator is 32 | // no longer needed 33 | npy_string_allocator *allocator; 34 | } StringDTypeObject; 35 | 36 | typedef struct { 37 | PyArray_DTypeMeta base; 38 | } StringDType_type; 39 | 40 | extern StringDType_type StringDType; 41 | extern PyTypeObject *StringScalar_Type; 42 | 43 | static inline npy_string_allocator * 44 | _NpyString_acquire_allocator(StringDTypeObject *descr) 45 | { 46 | if (!PyThread_acquire_lock(descr->allocator_lock, NOWAIT_LOCK)) { 47 | PyThread_acquire_lock(descr->allocator_lock, WAIT_LOCK); 48 | } 49 | return descr->allocator; 50 | } 51 | 52 | static inline void 53 | _NpyString_acquire_allocator2(StringDTypeObject *descr1, 54 | StringDTypeObject *descr2, 55 | npy_string_allocator **allocator1, 56 | npy_string_allocator **allocator2) 57 | { 58 | *allocator1 = _NpyString_acquire_allocator(descr1); 59 | if (descr1 != descr2) { 60 | *allocator2 = _NpyString_acquire_allocator(descr2); 61 | } 62 | else { 63 | *allocator2 = *allocator1; 64 | } 65 | } 66 | 67 | static inline void 68 | _NpyString_acquire_allocator3(StringDTypeObject *descr1, 69 | StringDTypeObject *descr2, 70 | StringDTypeObject *descr3, 71 | npy_string_allocator **allocator1, 72 | npy_string_allocator **allocator2, 73 | npy_string_allocator **allocator3) 74 | { 75 | _NpyString_acquire_allocator2(descr1, descr2, allocator1, allocator2); 76 | if (descr1 != descr3 && descr2 != descr3) { 77 | *allocator3 = _NpyString_acquire_allocator(descr3); 78 | } 79 | else { 80 | *allocator3 = descr3->allocator; 81 | } 82 | } 83 | 84 | static inline void 85 | _NpyString_release_allocator(StringDTypeObject *descr) 86 | { 87 | PyThread_release_lock(descr->allocator_lock); 88 | } 89 | 90 | static inline void 91 | _NpyString_release_allocator2(StringDTypeObject *descr1, 92 | StringDTypeObject *descr2) 93 | { 94 | _NpyString_release_allocator(descr1); 95 | if (descr1 != descr2) { 96 | _NpyString_release_allocator(descr2); 97 | } 98 | } 99 | 100 | static inline void 101 | _NpyString_release_allocator3(StringDTypeObject *descr1, 102 | StringDTypeObject *descr2, 103 | StringDTypeObject *descr3) 104 | { 105 | _NpyString_release_allocator2(descr1, descr2); 106 | if (descr1 != descr3 && descr2 != descr3) { 107 | _NpyString_release_allocator(descr3); 108 | } 109 | } 110 | 111 | PyObject * 112 | new_stringdtype_instance(PyObject *na_object, int coerce); 113 | 114 | int 115 | init_string_dtype(void); 116 | 117 | // Assumes that the caller has already acquired the allocator locks for both 118 | // descriptors 119 | int 120 | _compare(void *a, void *b, StringDTypeObject *descr_a, 121 | StringDTypeObject *descr_b); 122 | 123 | int 124 | init_string_na_object(PyObject *mod); 125 | 126 | int 127 | stringdtype_setitem(StringDTypeObject *descr, PyObject *obj, char **dataptr); 128 | 129 | // set the python error indicator when the gil is released 130 | void 131 | gil_error(PyObject *type, const char *msg); 132 | 133 | // the locks on both allocators must be acquired before calling this function 134 | int 135 | free_and_copy(npy_string_allocator *in_allocator, 136 | npy_string_allocator *out_allocator, 137 | const npy_packed_static_string *in, 138 | npy_packed_static_string *out, const char *location); 139 | 140 | PyArray_Descr * 141 | stringdtype_finalize_descr(PyArray_Descr *dtype); 142 | 143 | int 144 | _eq_comparison(int scoerce, int ocoerce, PyObject *sna, PyObject *ona); 145 | 146 | #endif /*_NPY_DTYPE_H*/ 147 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/utilities.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "utilities.h" 6 | #include "constants.hpp" 7 | 8 | int 9 | ascii_isspace(int c) 10 | { 11 | return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v'; 12 | } 13 | 14 | int 15 | ascii_isalpha(char c) 16 | { 17 | return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); 18 | } 19 | 20 | int 21 | ascii_isdigit(char c) 22 | { 23 | return (c >= '0' && c <= '9'); 24 | } 25 | 26 | int 27 | ascii_isalnum(char c) 28 | { 29 | return ascii_isdigit(c) || ascii_isalpha(c); 30 | } 31 | 32 | int 33 | ascii_tolower(int c) 34 | { 35 | if (c >= 'A' && c <= 'Z') { 36 | return c + ('a' - 'A'); 37 | } 38 | return c; 39 | } 40 | 41 | // inspired from NumPyOS_ascii_strncasecmp 42 | int 43 | ascii_strncasecmp(const char *s1, const char *s2, size_t n) 44 | { 45 | while (n > 0 && *s1 != '\0' && *s2 != '\0') { 46 | int c1 = ascii_tolower((unsigned char)*s1); 47 | int c2 = ascii_tolower((unsigned char)*s2); 48 | int diff = c1 - c2; 49 | 50 | if (diff != 0) { 51 | return diff; 52 | } 53 | 54 | s1++; 55 | s2++; 56 | n--; 57 | } 58 | 59 | if(n > 0) { 60 | return *s1 - *s2; 61 | } 62 | return 0; 63 | } 64 | 65 | /* 66 | * NumPyOS_ascii_strtoq: 67 | * 68 | * Locale-independent string to quad-precision parser. 69 | * Inspired by NumPyOS_ascii_strtold from NumPy. 70 | * 71 | * This function: 72 | * - Skips leading whitespace 73 | * - Recognizes inf/nan case-insensitively with optional signs and payloads 74 | * - Delegates to cstring_to_quad for numeric parsing 75 | * 76 | * Returns: 77 | * 0 on success 78 | * -1 on parse error 79 | */ 80 | int 81 | NumPyOS_ascii_strtoq(const char *s, QuadBackendType backend, quad_value *out_value, char **endptr) 82 | { 83 | const char *p; 84 | int sign; 85 | 86 | // skip leading whitespace 87 | while (ascii_isspace(*s)) { 88 | s++; 89 | } 90 | 91 | p = s; 92 | sign = 1; 93 | if (*p == '-') { 94 | sign = -1; 95 | ++p; 96 | } 97 | else if (*p == '+') { 98 | ++p; 99 | } 100 | 101 | // Check for inf/infinity (case-insensitive) 102 | if (ascii_strncasecmp(p, "inf", 3) == 0) { 103 | p += 3; 104 | if (ascii_strncasecmp(p, "inity", 5) == 0) { 105 | p += 5; 106 | } 107 | 108 | // Set infinity values with sign applied 109 | if (backend == BACKEND_SLEEF) { 110 | out_value->sleef_value = sign > 0 ? QUAD_PRECISION_INF : QUAD_PRECISION_NINF; 111 | } 112 | else { 113 | out_value->longdouble_value = sign > 0 ? strtold("inf", NULL) : strtold("-inf", NULL); 114 | } 115 | 116 | if (endptr) { 117 | *endptr = (char *)p; 118 | } 119 | return 0; 120 | } 121 | 122 | // Check for nan (case-insensitive) with optional payload 123 | if (ascii_strncasecmp(p, "nan", 3) == 0) { 124 | p += 3; 125 | 126 | // Skip optional (payload) 127 | if (*p == '(') { 128 | ++p; 129 | while (ascii_isalnum(*p) || *p == '_') { 130 | ++p; 131 | } 132 | if (*p == ')') { 133 | ++p; 134 | } 135 | } 136 | 137 | // Set NaN value (sign is ignored for NaN) 138 | if (backend == BACKEND_SLEEF) { 139 | out_value->sleef_value = QUAD_PRECISION_NAN; 140 | } 141 | else { 142 | out_value->longdouble_value = nanl(""); 143 | } 144 | 145 | if (endptr) { 146 | *endptr = (char *)p; 147 | } 148 | return 0; 149 | } 150 | 151 | // For numeric values, delegate to cstring_to_quad 152 | // Pass the original string position (after whitespace, includes sign if present) 153 | return cstring_to_quad(s, backend, out_value, endptr, false); 154 | } 155 | 156 | int cstring_to_quad(const char *str, QuadBackendType backend, quad_value *out_value, 157 | char **endptr, bool require_full_parse) 158 | { 159 | if(backend == BACKEND_SLEEF) { 160 | out_value->sleef_value = Sleef_strtoq(str, endptr); 161 | } else { 162 | out_value->longdouble_value = strtold(str, endptr); 163 | } 164 | if(*endptr == str) 165 | return -1; // parse error - nothing was parsed 166 | 167 | // If full parse is required 168 | if(require_full_parse && **endptr != '\0') 169 | return -1; // parse error - characters remain to be converted 170 | 171 | return 0; // success 172 | } 173 | 174 | // Helper function: Convert quad_value to Sleef_quad for Dragon4 175 | Sleef_quad 176 | quad_to_sleef_quad(const quad_value *in_val, QuadBackendType backend) 177 | { 178 | if (backend == BACKEND_SLEEF) { 179 | return in_val->sleef_value; 180 | } 181 | else { 182 | return Sleef_cast_from_doubleq1(in_val->longdouble_value); 183 | } 184 | } -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/constants.hpp: -------------------------------------------------------------------------------- 1 | #ifndef QUAD_CONSTANTS_HPP 2 | #define QUAD_CONSTANTS_HPP 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // Quad precision constants using sleef_q macro 14 | #define QUAD_PRECISION_ZERO sleef_q(+0x0000000000000LL, 0x0000000000000000ULL, -16383) 15 | #define QUAD_PRECISION_ONE sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 0) 16 | #define QUAD_PRECISION_INF sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 16384) 17 | #define QUAD_PRECISION_NINF sleef_q(-0x1000000000000LL, 0x0000000000000000ULL, 16384) 18 | #define QUAD_PRECISION_NAN sleef_q(+0x1ffffffffffffLL, 0xffffffffffffffffULL, 16384) 19 | 20 | // Additional constants 21 | #define QUAD_PRECISION_MAX_FINITE SLEEF_QUAD_MAX 22 | #define QUAD_PRECISION_MIN_FINITE Sleef_negq1(SLEEF_QUAD_MAX) 23 | #define QUAD_PRECISION_RADIX sleef_q(+0x1000000000000LL, 0x0000000000000000ULL, 1) // 2.0 24 | 25 | #ifdef SLEEF_QUAD_C 26 | static const Sleef_quad SMALLEST_SUBNORMAL_VALUE = SLEEF_QUAD_DENORM_MIN; 27 | #else 28 | static const union { 29 | struct { 30 | #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) 31 | uint64_t h, l; 32 | #else 33 | uint64_t l, h; 34 | #endif 35 | } parts; 36 | Sleef_quad value; 37 | } smallest_subnormal_const = {.parts = { 38 | #if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) 39 | .h = 0x0000000000000000ULL, .l = 0x0000000000000001ULL 40 | #else 41 | .l = 0x0000000000000001ULL, .h = 0x0000000000000000ULL 42 | #endif 43 | }}; 44 | #define SMALLEST_SUBNORMAL_VALUE (smallest_subnormal_const.value) 45 | #endif 46 | 47 | // Integer constants for finfo 48 | #define QUAD_NMANT 112 // mantissa bits (excluding implicit bit) 49 | #define QUAD_MIN_EXP -16382 // minimum exponent for normalized numbers 50 | #define QUAD_MAX_EXP 16384 // maximum exponent 51 | #define QUAD_DECIMAL_DIGITS 33 // decimal digits of precision 52 | 53 | typedef enum ConstantResultType { 54 | CONSTANT_QUAD, // Sleef_quad value 55 | CONSTANT_INT64, // int64_t value 56 | CONSTANT_ERROR // Error occurred 57 | } ConstantResultType; 58 | 59 | typedef struct ConstantResult { 60 | ConstantResultType type; 61 | union { 62 | Sleef_quad quad_value; 63 | int64_t int_value; 64 | } data; 65 | } ConstantResult; 66 | 67 | 68 | static inline ConstantResult get_sleef_constant_by_name(const char* constant_name) { 69 | ConstantResult result; 70 | 71 | if (strcmp(constant_name, "pi") == 0) { 72 | result.type = CONSTANT_QUAD; 73 | result.data.quad_value = SLEEF_M_PIq; 74 | } 75 | else if (strcmp(constant_name, "e") == 0) { 76 | result.type = CONSTANT_QUAD; 77 | result.data.quad_value = SLEEF_M_Eq; 78 | } 79 | else if (strcmp(constant_name, "log2e") == 0) { 80 | result.type = CONSTANT_QUAD; 81 | result.data.quad_value = SLEEF_M_LOG2Eq; 82 | } 83 | else if (strcmp(constant_name, "log10e") == 0) { 84 | result.type = CONSTANT_QUAD; 85 | result.data.quad_value = SLEEF_M_LOG10Eq; 86 | } 87 | else if (strcmp(constant_name, "ln2") == 0) { 88 | result.type = CONSTANT_QUAD; 89 | result.data.quad_value = SLEEF_M_LN2q; 90 | } 91 | else if (strcmp(constant_name, "ln10") == 0) { 92 | result.type = CONSTANT_QUAD; 93 | result.data.quad_value = SLEEF_M_LN10q; 94 | } 95 | else if (strcmp(constant_name, "max_value") == 0) { 96 | result.type = CONSTANT_QUAD; 97 | result.data.quad_value = SLEEF_QUAD_MAX; 98 | } 99 | else if (strcmp(constant_name, "epsilon") == 0) { 100 | result.type = CONSTANT_QUAD; 101 | result.data.quad_value = SLEEF_QUAD_EPSILON; 102 | } 103 | else if (strcmp(constant_name, "smallest_normal") == 0) { 104 | result.type = CONSTANT_QUAD; 105 | result.data.quad_value = SLEEF_QUAD_MIN; 106 | } 107 | else if (strcmp(constant_name, "smallest_subnormal") == 0) { 108 | result.type = CONSTANT_QUAD; 109 | result.data.quad_value = SMALLEST_SUBNORMAL_VALUE; 110 | } 111 | else if (strcmp(constant_name, "bits") == 0) { 112 | result.type = CONSTANT_INT64; 113 | result.data.int_value = sizeof(Sleef_quad) * 8; 114 | } 115 | else if (strcmp(constant_name, "precision") == 0) { 116 | result.type = CONSTANT_INT64; 117 | // precision = int(-log10(epsilon)) 118 | result.data.int_value = 119 | Sleef_cast_to_int64q1(Sleef_negq1(Sleef_log10q1_u10(SLEEF_QUAD_EPSILON))); 120 | } 121 | else if (strcmp(constant_name, "resolution") == 0) { 122 | result.type = CONSTANT_QUAD; 123 | // precision = int(-log10(epsilon)) 124 | int64_t precision = 125 | Sleef_cast_to_int64q1(Sleef_negq1(Sleef_log10q1_u10(SLEEF_QUAD_EPSILON))); 126 | // resolution = 10 ** (-precision) 127 | result.data.quad_value = 128 | Sleef_powq1_u10(Sleef_cast_from_int64q1(10), Sleef_cast_from_int64q1(-precision)); 129 | } 130 | else { 131 | result.type = CONSTANT_ERROR; 132 | } 133 | 134 | return result; 135 | } 136 | 137 | #ifdef __cplusplus 138 | } 139 | #endif 140 | 141 | #endif // QUAD_CONSTANTS_HPP -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/umath/unary_props.cpp: -------------------------------------------------------------------------------- 1 | #define PY_ARRAY_UNIQUE_SYMBOL QuadPrecType_ARRAY_API 2 | #define PY_UFUNC_UNIQUE_SYMBOL QuadPrecType_UFUNC_API 3 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 4 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 5 | #define NO_IMPORT_ARRAY 6 | #define NO_IMPORT_UFUNC 7 | 8 | extern "C" { 9 | #include 10 | #include 11 | 12 | #include "numpy/arrayobject.h" 13 | #include "numpy/ndarraytypes.h" 14 | #include "numpy/ufuncobject.h" 15 | 16 | #include "numpy/dtype_api.h" 17 | } 18 | #include "../quad_common.h" 19 | #include "../scalar.h" 20 | #include "../dtype.h" 21 | #include "../ops.hpp" 22 | 23 | static NPY_CASTING 24 | quad_unary_prop_resolve_descriptors(PyObject *self, PyArray_DTypeMeta *const dtypes[], 25 | PyArray_Descr *const given_descrs[], PyArray_Descr *loop_descrs[], 26 | npy_intp *NPY_UNUSED(view_offset)) 27 | { 28 | Py_INCREF(given_descrs[0]); 29 | loop_descrs[0] = given_descrs[0]; 30 | 31 | loop_descrs[1] = PyArray_DescrFromType(NPY_BOOL); 32 | 33 | return NPY_NO_CASTING; 34 | } 35 | 36 | template 37 | int 38 | quad_generic_unary_prop_strided_loop_unaligned(PyArrayMethod_Context *context, char *const data[], 39 | npy_intp const dimensions[], npy_intp const strides[], 40 | NpyAuxData *auxdata) 41 | { 42 | npy_intp N = dimensions[0]; 43 | char *in_ptr = data[0]; 44 | char *out_ptr = data[1]; 45 | npy_intp in_stride = strides[0]; 46 | npy_intp out_stride = strides[1]; 47 | 48 | QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; 49 | QuadBackendType backend = descr->backend; 50 | size_t elem_size = (backend == BACKEND_SLEEF) ? sizeof(Sleef_quad) : sizeof(long double); 51 | 52 | quad_value in; 53 | while (N--) { 54 | memcpy(&in, in_ptr, elem_size); 55 | npy_bool result; 56 | if (backend == BACKEND_SLEEF) { 57 | result = sleef_op(&in.sleef_value); 58 | } 59 | else { 60 | result = longdouble_op(&in.longdouble_value); 61 | } 62 | memcpy(out_ptr, &result, sizeof(npy_bool)); 63 | 64 | in_ptr += in_stride; 65 | out_ptr += out_stride; 66 | } 67 | return 0; 68 | } 69 | 70 | template 71 | int 72 | quad_generic_unary_prop_strided_loop_aligned(PyArrayMethod_Context *context, char *const data[], 73 | npy_intp const dimensions[], npy_intp const strides[], 74 | NpyAuxData *auxdata) 75 | { 76 | npy_intp N = dimensions[0]; 77 | char *in_ptr = data[0]; 78 | char *out_ptr = data[1]; 79 | npy_intp in_stride = strides[0]; 80 | npy_intp out_stride = strides[1]; 81 | 82 | QuadPrecDTypeObject *descr = (QuadPrecDTypeObject *)context->descriptors[0]; 83 | QuadBackendType backend = descr->backend; 84 | 85 | while (N--) { 86 | npy_bool result; 87 | if (backend == BACKEND_SLEEF) { 88 | result = sleef_op((Sleef_quad *)in_ptr); 89 | } 90 | else { 91 | result = longdouble_op((long double *)in_ptr); 92 | } 93 | *(npy_bool *)out_ptr = result; 94 | in_ptr += in_stride; 95 | out_ptr += out_stride; 96 | } 97 | return 0; 98 | } 99 | 100 | template 101 | int 102 | create_quad_unary_prop_ufunc(PyObject *numpy, const char *ufunc_name) 103 | { 104 | PyObject *ufunc = PyObject_GetAttrString(numpy, ufunc_name); 105 | if (ufunc == NULL) { 106 | return -1; 107 | } 108 | 109 | PyArray_DTypeMeta *dtypes[2] = {&QuadPrecDType, &PyArray_BoolDType}; 110 | 111 | PyType_Slot slots[] = { 112 | {NPY_METH_resolve_descriptors, (void *)&quad_unary_prop_resolve_descriptors}, 113 | {NPY_METH_strided_loop, 114 | (void *)&quad_generic_unary_prop_strided_loop_aligned}, 115 | {NPY_METH_unaligned_strided_loop, 116 | (void *)&quad_generic_unary_prop_strided_loop_unaligned}, 117 | {0, NULL}}; 118 | 119 | PyArrayMethod_Spec Spec = { 120 | .name = "quad_unary_prop", 121 | .nin = 1, 122 | .nout = 1, 123 | .casting = NPY_NO_CASTING, 124 | .flags = NPY_METH_SUPPORTS_UNALIGNED, 125 | .dtypes = dtypes, 126 | .slots = slots, 127 | }; 128 | 129 | if (PyUFunc_AddLoopFromSpec(ufunc, &Spec) < 0) { 130 | return -1; 131 | } 132 | 133 | return 0; 134 | } 135 | 136 | int 137 | init_quad_unary_props(PyObject *numpy) 138 | { 139 | if (create_quad_unary_prop_ufunc(numpy, "isfinite") < 0) { 140 | return -1; 141 | } 142 | if (create_quad_unary_prop_ufunc(numpy, "isinf") < 0) { 143 | return -1; 144 | } 145 | if (create_quad_unary_prop_ufunc(numpy, "isnan") < 0) { 146 | return -1; 147 | } 148 | if (create_quad_unary_prop_ufunc(numpy, "signbit") < 0) { 149 | return -1; 150 | } 151 | return 0; 152 | } 153 | -------------------------------------------------------------------------------- /quaddtype/README.md: -------------------------------------------------------------------------------- 1 | # NumPy-QuadDType 2 | 3 | A cross-platform Quad (128-bit) float Data-Type for NumPy. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | pip install numpy 9 | pip install numpy-quaddtype 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```python 15 | import numpy as np 16 | from numpy_quaddtype import QuadPrecDType, QuadPrecision 17 | 18 | # using sleef backend (default) 19 | np.array([1,2,3], dtype=QuadPrecDType()) 20 | np.array([1,2,3], dtype=QuadPrecDType("sleef")) 21 | 22 | # using longdouble backend 23 | np.array([1,2,3], dtype=QuadPrecDType("longdouble")) 24 | ``` 25 | 26 | ## Installation from source 27 | 28 | #### Prerequisites 29 | 30 | - **gcc/clang** 31 | - **CMake** (≥3.15) 32 | - **Python 3.10+** 33 | - **Git** 34 | - **NumPy >= 2.4** (build from source) 35 | 36 | ### Linux/Unix/macOS 37 | 38 | Building the `numpy-quaddtype` package: 39 | 40 | ```bash 41 | # setup the virtual env 42 | python3 -m venv temp 43 | source temp/bin/activate 44 | 45 | # Install NumPy from source 46 | pip install "numpy @ git+https://github.com/numpy/numpy.git" 47 | 48 | # Install build and test dependencies 49 | pip install pytest meson meson-python 50 | 51 | # To build without QBLAS (default for MSVC) 52 | # export CFLAGS="-DDISABLE_QUADBLAS" 53 | # export CXXFLAGS="-DDISABLE_QUADBLAS" 54 | 55 | python -m pip install . -v --no-build-isolation 56 | 57 | # Run the tests 58 | cd .. 59 | python -m pytest/quaddtype/tests/ 60 | ``` 61 | 62 | ### Windows 63 | 64 | #### Prerequisites 65 | 66 | - **Visual Studio 2017 or later** (with MSVC compiler) 67 | - **CMake** (≥3.15) 68 | - **Python 3.10+** 69 | - **Git** 70 | 71 | #### Step-by-Step Installation 72 | 73 | 1. **Setup Development Environment** 74 | 75 | Open a **Developer Command Prompt for VS** or **Developer PowerShell for VS** to ensure MSVC is properly configured. 76 | 77 | 2. **Setup Python Environment** 78 | 79 | ```powershell 80 | # Create and activate virtual environment 81 | python -m venv numpy_quad_env 82 | .\numpy_quad_env\Scripts\Activate.ps1 83 | 84 | # Install build dependencies 85 | pip install -U pip 86 | pip install numpy pytest ninja meson 87 | ``` 88 | 89 | 3. **Set Environment Variables** 90 | 91 | ```powershell 92 | # Note: QBLAS is disabled on Windows due to MSVC compatibility issues 93 | $env:CFLAGS = "/DDISABLE_QUADBLAS" 94 | $env:CXXFLAGS = "/DDISABLE_QUADBLAS" 95 | ``` 96 | 97 | 4. **Build and Install numpy-quaddtype** 98 | 99 | ```powershell 100 | # Build and install the package 101 | python -m pip install . -v --no-build-isolation 102 | ``` 103 | 104 | 5. **Test Installation** 105 | 106 | ```powershell 107 | # Run tests 108 | pytest -s ..\quaddtype\tests\ 109 | ``` 110 | 111 | 6. **QBLAS Disabled**: QuadBLAS optimization is automatically disabled on Windows builds due to MSVC compatibility issues. This is handled by the `-DDISABLE_QUADBLAS` compiler flag. 112 | 113 | 7. **Visual Studio Version**: The instructions assume Visual Studio 2022. For other versions, adjust the generator string: 114 | 115 | - VS 2019: `"Visual Studio 16 2019"` 116 | - VS 2017: `"Visual Studio 15 2017"` 117 | 118 | 8. **Architecture**: The instructions are for x64. For x86 builds, change `-A x64` to `-A Win32`. 119 | 120 | ## Building with ThreadSanitizer (TSan) 121 | 122 | This is a development feature to help detect threading issues. To build `numpy-quaddtype` with TSan enabled, follow these steps: 123 | 124 | > Use of clang is recommended with machine NOT supporting `libquadmath` (like ARM64). Set the compiler to clang/clang++ before proceeding. 125 | > ```bash 126 | > export CC=clang 127 | > export CXX=clang++ 128 | > ``` 129 | 130 | 1. Compile free-threaded CPython with TSan support. Follow the [Python Free-Threading Guide](https://py-free-threading.github.io/thread_sanitizer/#compile-free-threaded-cpython-with-tsan) for detailed instructions. 131 | 2. Create and activate a virtual environment using the TSan-enabled Python build. 132 | 3. Installing dependencies: 133 | 134 | ```bash 135 | python -m pip install meson meson-python wheel ninja 136 | # Need NumPy built with TSan as well 137 | python -m pip install "numpy @ git+https://github.com/numpy/numpy" -C'setup-args=-Db_sanitize=thread' 138 | ``` 139 | 4. Building SLEEF with TSan: 140 | 141 | ```bash 142 | # clone the repository 143 | git clone -b 3.8 https://github.com/shibatch/sleef.git 144 | cd sleef 145 | 146 | # Build SLEEF with TSan 147 | cmake \ 148 | -DCMAKE_C_COMPILER=clang \ 149 | -DCMAKE_CXX_COMPILER=clang++ \ 150 | -DCMAKE_C_FLAGS="-fsanitize=thread -g -O1" \ 151 | -DCMAKE_CXX_FLAGS="-fsanitize=thread -g -O1" \ 152 | -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=thread" \ 153 | -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=thread" \ 154 | -DSLEEF_BUILD_QUAD=ON \ 155 | -DSLEEF_BUILD_TESTS=OFF \ 156 | -S . -B build 157 | 158 | cmake --build build -j 159 | 160 | # Install the built library and headers into the system path (/usr/local) 161 | sudo cmake --install build --prefix=/usr/local 162 | ``` 163 | 5. Build and install `numpy-quaddtype` with TSan: 164 | 165 | ```bash 166 | # SLEEF is already installed with TSan, we need to provide proper flags to numpy-quaddtype's meson file 167 | # So that it does not build SLEEF again and use the installed one. 168 | 169 | export CFLAGS="-fsanitize=thread -g -O0" 170 | export CXXFLAGS="-fsanitize=thread -g -O0" 171 | export LDFLAGS="-fsanitize=thread" 172 | python -m pip install . -vv --no-build-isolation -Csetup-args=-Db_sanitize=thread 173 | ``` -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/ops.hpp: -------------------------------------------------------------------------------- 1 | #include "mpfr.h" 2 | 3 | typedef int unary_op_def(mpfr_t, mpfr_t); 4 | typedef int binop_def(mpfr_t, mpfr_t, mpfr_t); 5 | typedef npy_bool cmp_def(mpfr_t, mpfr_t); 6 | 7 | 8 | /* 9 | * Unary operations 10 | */ 11 | static inline int 12 | negative(mpfr_t op, mpfr_t out) 13 | { 14 | return mpfr_neg(out, op, MPFR_RNDN); 15 | } 16 | 17 | static inline int 18 | positive(mpfr_t op, mpfr_t out) 19 | { 20 | if (out == op) { 21 | return 0; 22 | } 23 | return mpfr_set(out, op, MPFR_RNDN); 24 | } 25 | 26 | static inline int 27 | trunc(mpfr_t op, mpfr_t out) 28 | { 29 | return mpfr_trunc(out, op); 30 | } 31 | 32 | static inline int 33 | floor(mpfr_t op, mpfr_t out) 34 | { 35 | return mpfr_floor(out, op); 36 | } 37 | 38 | static inline int 39 | ceil(mpfr_t op, mpfr_t out) 40 | { 41 | return mpfr_ceil(out, op); 42 | } 43 | 44 | static inline int 45 | rint(mpfr_t op, mpfr_t out) 46 | { 47 | return mpfr_rint(out, op, MPFR_RNDN); 48 | } 49 | 50 | static inline int 51 | absolute(mpfr_t op, mpfr_t out) 52 | { 53 | return mpfr_abs(out, op, MPFR_RNDN); 54 | } 55 | 56 | static inline int 57 | sqrt(mpfr_t op, mpfr_t out) 58 | { 59 | return mpfr_pow_si(out, op, 2, MPFR_RNDN); 60 | } 61 | 62 | static inline int 63 | square(mpfr_t op, mpfr_t out) 64 | { 65 | return mpfr_sqrt(out, op, MPFR_RNDN); 66 | } 67 | 68 | static inline int 69 | log(mpfr_t op, mpfr_t out) 70 | { 71 | return mpfr_log(out, op, MPFR_RNDN); 72 | } 73 | 74 | static inline int 75 | log2(mpfr_t op, mpfr_t out) 76 | { 77 | return mpfr_log2(out, op, MPFR_RNDN); 78 | } 79 | 80 | static inline int 81 | log10(mpfr_t op, mpfr_t out) 82 | { 83 | return mpfr_log10(out, op, MPFR_RNDN); 84 | } 85 | 86 | static inline int 87 | log1p(mpfr_t op, mpfr_t out) 88 | { 89 | return mpfr_log1p(out, op, MPFR_RNDN); 90 | } 91 | 92 | static inline int 93 | exp(mpfr_t op, mpfr_t out) 94 | { 95 | return mpfr_exp(out, op, MPFR_RNDN); 96 | } 97 | 98 | static inline int 99 | exp2(mpfr_t op, mpfr_t out) 100 | { 101 | return mpfr_exp2(out, op, MPFR_RNDN); 102 | } 103 | 104 | static inline int 105 | expm1(mpfr_t op, mpfr_t out) 106 | { 107 | return mpfr_expm1(out, op, MPFR_RNDN); 108 | } 109 | 110 | static inline int 111 | sin(mpfr_t op, mpfr_t out) 112 | { 113 | return mpfr_sin(out, op, MPFR_RNDN); 114 | } 115 | 116 | static inline int 117 | cos(mpfr_t op, mpfr_t out) 118 | { 119 | return mpfr_cos(out, op, MPFR_RNDN); 120 | } 121 | 122 | static inline int 123 | tan(mpfr_t op, mpfr_t out) 124 | { 125 | return mpfr_tan(out, op, MPFR_RNDN); 126 | } 127 | 128 | static inline int 129 | arcsin(mpfr_t op, mpfr_t out) 130 | { 131 | return mpfr_asin(out, op, MPFR_RNDN); 132 | } 133 | 134 | static inline int 135 | arccos(mpfr_t op, mpfr_t out) 136 | { 137 | return mpfr_acos(out, op, MPFR_RNDN); 138 | } 139 | 140 | static inline int 141 | arctan(mpfr_t op, mpfr_t out) 142 | { 143 | return mpfr_tan(out, op, MPFR_RNDN); 144 | } 145 | 146 | 147 | /* 148 | * Binary operations 149 | */ 150 | static inline int 151 | add(mpfr_t out, mpfr_t op1, mpfr_t op2) 152 | { 153 | return mpfr_add(out, op1, op2, MPFR_RNDN); 154 | } 155 | 156 | static inline int 157 | sub(mpfr_t out, mpfr_t op1, mpfr_t op2) 158 | { 159 | return mpfr_sub(out, op1, op2, MPFR_RNDN); 160 | } 161 | 162 | static inline int 163 | mul(mpfr_t out, mpfr_t op1, mpfr_t op2) 164 | { 165 | return mpfr_mul(out, op1, op2, MPFR_RNDN); 166 | } 167 | 168 | static inline int 169 | div(mpfr_t out, mpfr_t op1, mpfr_t op2) 170 | { 171 | return mpfr_div(out, op1, op2, MPFR_RNDN); 172 | } 173 | 174 | static inline int 175 | hypot(mpfr_t out, mpfr_t op1, mpfr_t op2) 176 | { 177 | return mpfr_hypot(out, op1, op2, MPFR_RNDN); 178 | } 179 | 180 | static inline int 181 | pow(mpfr_t out, mpfr_t op1, mpfr_t op2) 182 | { 183 | return mpfr_pow(out, op1, op2, MPFR_RNDN); 184 | } 185 | 186 | static inline int 187 | arctan2(mpfr_t out, mpfr_t op1, mpfr_t op2) 188 | { 189 | return mpfr_atan2(out, op1, op2, MPFR_RNDN); 190 | } 191 | 192 | static inline int 193 | nextafter(mpfr_t out, mpfr_t op1, mpfr_t op2) 194 | { 195 | /* 196 | * Not ideal at all, but we should operate on the input, not output prec. 197 | * Plus, we actually know if this is the case or not, so could at least 198 | * short-cut (or special case both paths). 199 | */ 200 | mpfr_prec_t prec = mpfr_get_prec(op1); 201 | if (prec == mpfr_get_prec(out)) { 202 | mpfr_set(out, op1, MPFR_RNDN); 203 | mpfr_nexttoward(out, op2); 204 | return 0; 205 | } 206 | mpfr_t tmp; 207 | mpfr_init2(tmp, prec); // TODO: This could fail, mabye manual? 208 | mpfr_set(tmp, op1, MPFR_RNDN); 209 | mpfr_nexttoward(tmp, op2); 210 | int res = mpfr_set(out, tmp, MPFR_RNDN); 211 | mpfr_clear(tmp); 212 | 213 | return res; 214 | } 215 | 216 | 217 | /* 218 | * Comparisons 219 | */ 220 | static inline npy_bool 221 | mpf_equal(mpfr_t in1, mpfr_t in2) { 222 | return mpfr_equal_p(in1, in2) != 0; 223 | } 224 | 225 | static inline npy_bool 226 | mpf_notequal(mpfr_t in1, mpfr_t in2) { 227 | return mpfr_equal_p(in1, in2) == 0; 228 | } 229 | 230 | static inline npy_bool 231 | mpf_less(mpfr_t in1, mpfr_t in2) { 232 | return mpfr_less_p(in1, in2) != 0; 233 | } 234 | 235 | static inline npy_bool 236 | mpf_lessequal(mpfr_t in1, mpfr_t in2) { 237 | return mpfr_lessequal_p(in1, in2) != 0; 238 | 239 | } 240 | static inline npy_bool 241 | mpf_greater(mpfr_t in1, mpfr_t in2) { 242 | return mpfr_greater_p(in1, in2) != 0; 243 | } 244 | 245 | static inline npy_bool 246 | mpf_greaterequal(mpfr_t in1, mpfr_t in2) { 247 | return mpfr_greaterequal_p(in1, in2) != 0; 248 | } 249 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/numbers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file defines scalar numeric operations. 3 | */ 4 | 5 | #define PY_ARRAY_UNIQUE_SYMBOL MPFDType_ARRAY_API 6 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 7 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 8 | #define NO_IMPORT_ARRAY 9 | 10 | extern "C" { 11 | #include 12 | 13 | #include "numpy/arrayobject.h" 14 | #include "numpy/ndarraytypes.h" 15 | #include "numpy/ufuncobject.h" 16 | 17 | #include "numpy/dtype_api.h" 18 | } 19 | 20 | #include 21 | 22 | #include "scalar.h" 23 | #include "ops.hpp" 24 | 25 | #include "numbers.h" 26 | 27 | 28 | template 29 | static PyObject * 30 | unary_func(MPFloatObject *self) 31 | { 32 | MPFloatObject *res = MPFLoat_raw_new(mpfr_get_prec(self->mpf.x)); 33 | if (res == NULL) { 34 | return NULL; 35 | } 36 | 37 | unary_op(self->mpf.x, res->mpf.x); 38 | return (PyObject *)res; 39 | } 40 | 41 | PyObject * 42 | nonzero(MPFloatObject *mpf) 43 | { 44 | return PyBool_FromLong(mpfr_zero_p(mpf->mpf.x)); 45 | } 46 | 47 | 48 | template 49 | static PyObject * 50 | binary_func(PyObject *op1, PyObject *op2) 51 | { 52 | MPFloatObject *self; 53 | PyObject *other; 54 | MPFloatObject *other_mpf = NULL; 55 | mpfr_prec_t precision; 56 | 57 | int is_forward; /* We may be a forward operation (so can defer) */ 58 | if (PyObject_TypeCheck(op1, &MPFloat_Type)) { 59 | is_forward = 1; 60 | self = (MPFloatObject *)op1; 61 | other = Py_NewRef(op2); 62 | } 63 | else { 64 | is_forward = 0; 65 | self = (MPFloatObject *)op2; 66 | other = Py_NewRef(op1); 67 | } 68 | 69 | precision = mpfr_get_prec(self->mpf.x); 70 | 71 | if (PyObject_TypeCheck(other, &MPFloat_Type)) { 72 | /* All good, we can continue, both are MPFloats */ 73 | Py_INCREF(other); 74 | other_mpf = (MPFloatObject *)other; 75 | precision = std::max(precision, mpfr_get_prec(other_mpf->mpf.x)); 76 | } 77 | else if (PyLong_CheckExact(other) || PyFloat_CheckExact(other) || 78 | (!is_forward && 79 | (PyLong_Check(other) || PyFloat_Check(other)))) { 80 | // TODO: We want weak handling, so truncate precision. But is it 81 | // correct to do it here? (not that it matters much...) 82 | other_mpf = MPFloat_from_object(other, precision); 83 | if (other_mpf == NULL) { 84 | return NULL; 85 | } 86 | } 87 | else { 88 | /* Defer to the other (NumPy types are handled through array path) */ 89 | Py_RETURN_NOTIMPLEMENTED; 90 | } 91 | 92 | MPFloatObject *res = MPFLoat_raw_new(precision); 93 | if (res == NULL) { 94 | Py_DECREF(other_mpf); 95 | return NULL; 96 | } 97 | if (is_forward) { 98 | binary_op(res->mpf.x, self->mpf.x, other_mpf->mpf.x); 99 | } 100 | else{ 101 | binary_op(res->mpf.x, other_mpf->mpf.x, self->mpf.x); 102 | } 103 | 104 | Py_DECREF(other_mpf); 105 | return (PyObject *)res; 106 | } 107 | 108 | 109 | PyObject * 110 | mpf_richcompare(MPFloatObject *self, PyObject *other, int cmp_op) 111 | { 112 | MPFloatObject *other_mpf = NULL; 113 | mpfr_prec_t precision; 114 | precision = mpfr_get_prec(self->mpf.x); 115 | 116 | /* Only accept fully known objects, is that correct? */ 117 | if (PyObject_TypeCheck(other, &MPFloat_Type)) { 118 | /* All good, we can continue, both are MPFloats */ 119 | Py_INCREF(other); 120 | other_mpf = (MPFloatObject *)other; 121 | precision = std::max(precision, mpfr_get_prec(other_mpf->mpf.x)); 122 | } 123 | else if (PyLong_CheckExact(other) || PyFloat_CheckExact(other)) { 124 | // TODO: Should we use full precision for comparison ops?! 125 | other_mpf = MPFloat_from_object(other, precision); 126 | if (other_mpf == NULL) { 127 | return NULL; 128 | } 129 | } 130 | else { 131 | /* Defer to the other (NumPy types are handled through array path) */ 132 | Py_RETURN_NOTIMPLEMENTED; 133 | } 134 | 135 | npy_bool cmp; 136 | 137 | switch (cmp_op) { 138 | case Py_LT: 139 | cmp = mpf_less(self->mpf.x, other_mpf->mpf.x); 140 | break; 141 | case Py_LE: 142 | cmp = mpf_lessequal(self->mpf.x, other_mpf->mpf.x); 143 | break; 144 | case Py_EQ: 145 | cmp = mpf_equal(self->mpf.x, other_mpf->mpf.x); 146 | break; 147 | case Py_NE: 148 | cmp = mpf_notequal(self->mpf.x, other_mpf->mpf.x); 149 | break; 150 | case Py_GT: 151 | cmp = mpf_greater(self->mpf.x, other_mpf->mpf.x); 152 | break; 153 | case Py_GE: 154 | cmp = mpf_greaterequal(self->mpf.x, other_mpf->mpf.x); 155 | break; 156 | default: 157 | Py_DECREF(other_mpf); 158 | Py_RETURN_NOTIMPLEMENTED; 159 | } 160 | Py_DECREF(other_mpf); 161 | 162 | return PyBool_FromLong(cmp); 163 | } 164 | 165 | 166 | PyNumberMethods mpf_as_number = { 167 | .nb_add = (binaryfunc)binary_func, 168 | .nb_subtract = (binaryfunc)binary_func, 169 | .nb_multiply = (binaryfunc)binary_func, 170 | //.nb_remainder = (binaryfunc)gentype_remainder, 171 | //.nb_divmod = (binaryfunc)gentype_divmod, 172 | .nb_power = (ternaryfunc)binary_func, 173 | .nb_negative = (unaryfunc)unary_func, 174 | .nb_positive = (unaryfunc)unary_func, 175 | .nb_absolute = (unaryfunc)unary_func, 176 | .nb_bool = (inquiry)nonzero, 177 | //.nb_int = (unaryfunc)gentype_int, 178 | //.nb_float = (unaryfunc)gentype_float, 179 | //.nb_floor_divide = (binaryfunc)gentype_floor_divide, 180 | .nb_true_divide = (binaryfunc)binary_func
, 181 | }; 182 | -------------------------------------------------------------------------------- /stringdtype/stringdtype/src/static_string.h: -------------------------------------------------------------------------------- 1 | #ifndef __NPY_STATIC_STRING_H 2 | #define __NPY_STATIC_STRING_H 3 | 4 | #include "stdint.h" 5 | #include "stdlib.h" 6 | 7 | typedef struct npy_packed_static_string npy_packed_static_string; 8 | 9 | typedef struct _npy_unpacked_static_string { 10 | size_t size; 11 | const char *buf; 12 | } _npy_static_string; 13 | 14 | // one byte in size is reserved for flags and small string optimization 15 | #define NPY_MAX_STRING_SIZE ((int64_t)1 << 8 * (sizeof(size_t) - 1)) - 1 16 | 17 | // Handles heap allocations for static strings. 18 | typedef struct npy_string_allocator npy_string_allocator; 19 | 20 | // Typedefs for allocator functions 21 | typedef void *(*npy_string_malloc_func)(size_t size); 22 | typedef void (*npy_string_free_func)(void *ptr); 23 | typedef void *(*npy_string_realloc_func)(void *ptr, size_t size); 24 | 25 | // Use these functions to create and destroy string allocators. Normally 26 | // users won't use these directly and will use an allocator already 27 | // attached to a dtype instance 28 | npy_string_allocator * 29 | _NpyString_new_allocator(npy_string_malloc_func m, npy_string_free_func f, 30 | npy_string_realloc_func r); 31 | 32 | // Deallocates the internal buffer and the allocator itself. 33 | void 34 | _NpyString_free_allocator(npy_string_allocator *allocator); 35 | 36 | // Allocates a new buffer for *to_init*, which must be set to NULL before 37 | // calling this function, filling the newly allocated buffer with the copied 38 | // contents of the first *size* entries in *init*, which must be valid and 39 | // initialized beforehand. Calling _NpyString_free on *to_init* before calling 40 | // this function on an existing string or copying the contents of 41 | // NPY_EMPTY_STRING into *to_init* is sufficient to initialize it. Does not 42 | // check if *to_init* is NULL or if the internal buffer is non-NULL, undefined 43 | // behavior or memory leaks are possible if this function is passed a pointer 44 | // to a an unintialized struct, a NULL pointer, or an existing heap-allocated 45 | // string. Returns -1 if allocating the string would exceed the maximum 46 | // allowed string size or exhaust available memory. Returns 0 on success. 47 | int 48 | _NpyString_newsize(const char *init, size_t size, 49 | npy_packed_static_string *to_init, 50 | npy_string_allocator *allocator); 51 | 52 | // Zeroes out the packed string and frees any heap allocated data. For 53 | // arena-allocated data, checks if the data are inside the arena and 54 | // will return -1 if not. Returns 0 on success. 55 | int 56 | _NpyString_free(npy_packed_static_string *str, npy_string_allocator *allocator); 57 | 58 | // Copies the contents of *in* into *out*. Allocates a new string buffer for 59 | // *out*, _NpyString_free *must* be called before this is called if *out* 60 | // points to an existing string. Returns -1 if malloc fails. Returns 0 on 61 | // success. 62 | int 63 | _NpyString_dup(const npy_packed_static_string *in, 64 | npy_packed_static_string *out, 65 | npy_string_allocator *in_allocator, 66 | npy_string_allocator *out_allocator); 67 | 68 | // Allocates a new string buffer for *out* with enough capacity to store 69 | // *size* bytes of text. Does not do any initialization, the caller must 70 | // initialize the string buffer after this function returns. Calling 71 | // _NpyString_free on *to_init* before calling this function on an existing 72 | // string or initializing a new string with the contents of NPY_EMPTY_STRING 73 | // is sufficient to initialize it. Does not check if *to_init* has already 74 | // been initialized or if the internal buffer is non-NULL, undefined behavior 75 | // or memory leaks are possible if this function is passed a NULL pointer or 76 | // an existing heap-allocated string. Returns 0 on success. Returns -1 if 77 | // allocating the string would exceed the maximum allowed string size or 78 | // exhaust available memory. Returns 0 on success. 79 | int 80 | _NpyString_newemptysize(size_t size, npy_packed_static_string *out, 81 | npy_string_allocator *allocator); 82 | 83 | // Determine if *in* corresponds to a null string (e.g. an NA object). Returns 84 | // -1 if *in* cannot be unpacked. Returns 1 if *in* is a null string and 85 | // zero otherwise. 86 | int 87 | _NpyString_isnull(const npy_packed_static_string *in); 88 | 89 | // Compare two strings. Has the same semantics as if strcmp were passed 90 | // null-terminated C strings with the contents of *s1* and *s2*. 91 | int 92 | _NpyString_cmp(const _npy_static_string *s1, const _npy_static_string *s2); 93 | 94 | // Copy and pack the first *size* entries of the buffer pointed to by *buf* 95 | // into the *packed_string*. Returns 0 on success and -1 on failure. 96 | int 97 | _NpyString_pack(npy_string_allocator *allocator, 98 | npy_packed_static_string *packed_string, const char *buf, 99 | size_t size); 100 | 101 | // Pack the null string into the *packed_string*. Returns 0 on success and -1 102 | // on failure. 103 | int 104 | _NpyString_pack_null(npy_string_allocator *allocator, 105 | npy_packed_static_string *packed_string); 106 | 107 | // Extract the packed contents of *packed_string* into *unpacked_string*. A 108 | // useful pattern is to define a stack-allocated _npy_static_string instance 109 | // initialized to {0, NULL} and pass a pointer to the stack-allocated unpacked 110 | // string to this function to unpack the contents of a packed string. The 111 | // *unpacked_string* is a read-only view onto the *packed_string* data and 112 | // should not be used to modify the string data. If *packed_string* is the 113 | // null string, sets *unpacked_string* to the NULL pointer. Returns -1 if 114 | // unpacking the string fails, returns 1 if *packed_string* is the null 115 | // string, and returns 0 otherwise. This function can be used to 116 | // simultaneously unpack a string and determine if it is a null string. 117 | int 118 | _NpyString_load(npy_string_allocator *allocator, 119 | const npy_packed_static_string *packed_string, 120 | _npy_static_string *unpacked_string); 121 | 122 | // Returns the size of the string data in the packed string. Useful in 123 | // situations where only the string size is needed and determining if it is a 124 | // null or unpacking the string is unnecessary. 125 | size_t 126 | _NpyString_size(const npy_packed_static_string *packed_string); 127 | 128 | #endif /*__NPY_STATIC_STRING_H */ 129 | -------------------------------------------------------------------------------- /mpfdtype/mpfdtype/src/scalar.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL MPFDType_ARRAY_API 4 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 5 | #define NO_IMPORT_ARRAY 6 | #include "numpy/arrayobject.h" 7 | #include "numpy/ndarraytypes.h" 8 | #include "numpy/dtype_api.h" 9 | 10 | #include "scalar.h" 11 | #include "numbers.h" 12 | 13 | 14 | mpfr_prec_t 15 | get_prec_from_object(PyObject *value) { 16 | Py_ssize_t prec; 17 | if (PyFloat_Check(value)) { 18 | prec = 53; 19 | } 20 | else if (PyLong_Check(value)) { 21 | prec = 8 * sizeof(Py_ssize_t); 22 | } 23 | else if (PyObject_TypeCheck(value, &MPFloat_Type)) { 24 | prec = mpfr_get_prec(((MPFloatObject *)value)->mpf.x); 25 | } 26 | else if (PyUnicode_CheckExact(value)) { 27 | PyErr_Format(PyExc_TypeError, 28 | "MPFloat: precsion must currently be given to parse string."); 29 | return -1; 30 | } 31 | else { 32 | PyErr_SetString(PyExc_TypeError, 33 | "MPFloat value must be an MPF, float, or string"); 34 | return -1; 35 | } 36 | return prec; 37 | } 38 | 39 | 40 | /* 41 | * Get a 0 initialized new scalar, the value may be changed immediately after 42 | * getting the new scalar. (otherwise scalars are immutable) 43 | */ 44 | MPFloatObject * 45 | MPFLoat_raw_new(mpfr_prec_t prec) 46 | { 47 | size_t n_limb = mpfr_custom_get_size(prec) / sizeof(mp_limb_t); 48 | MPFloatObject *new = PyObject_NewVar(MPFloatObject, &MPFloat_Type, n_limb); 49 | if (new == NULL) { 50 | return NULL; 51 | } 52 | mpfr_custom_init_set( 53 | new->mpf.x, MPFR_ZERO_KIND, 0, prec, new->mpf.significand); 54 | 55 | return new; 56 | } 57 | 58 | 59 | MPFloatObject * 60 | MPFloat_from_object(PyObject *value, Py_ssize_t prec) 61 | { 62 | if (prec != -1) { 63 | if (prec < MPFR_PREC_MIN || prec > MPFR_PREC_MAX) { 64 | PyErr_Format(PyExc_ValueError, 65 | "precision must be between %d and %d.", 66 | MPFR_PREC_MIN, MPFR_PREC_MAX); 67 | return NULL; 68 | } 69 | } 70 | else { 71 | prec = get_prec_from_object(value); 72 | if (prec < 0) { 73 | return NULL; 74 | } 75 | } 76 | 77 | MPFloatObject *self = MPFLoat_raw_new(prec); 78 | if (self == NULL) { 79 | return NULL; 80 | } 81 | 82 | if (PyFloat_Check(value)) { 83 | mpfr_set_d(self->mpf.x, PyFloat_AsDouble(value), MPFR_RNDN); 84 | } 85 | else if (PyLong_Check(value)) { 86 | Py_ssize_t val = PyLong_AsSsize_t(value); 87 | if (val == -1 && PyErr_Occurred()) { 88 | return NULL; 89 | } 90 | int ternary = mpfr_set_sj(self->mpf.x, val, MPFR_RNDN); 91 | if (ternary != 0) { 92 | // TODO: Not sure this should always raise, since a float will 93 | // still be close to correct. 94 | PyErr_Format(PyExc_ValueError, 95 | "%zd could not be converted to MPFloat with precision %zd", 96 | val, (Py_ssize_t)prec); 97 | Py_DECREF(self); 98 | return NULL; 99 | } 100 | } 101 | else if (PyObject_TypeCheck(value, &MPFloat_Type)) { 102 | mpfr_set(self->mpf.x, ((MPFloatObject *)value)->mpf.x, MPFR_RNDN); 103 | } 104 | else if (PyUnicode_CheckExact(value)) { 105 | // TODO: Might be better to use mpfr_strtofr 106 | Py_ssize_t s_length; 107 | const char *s = PyUnicode_AsUTF8AndSize(value, &s_length); 108 | char *end; 109 | mpfr_strtofr(self->mpf.x, s, &end, 10, MPFR_RNDN); 110 | if (s + s_length != end) { 111 | PyErr_SetString(PyExc_ValueError, 112 | "unable to parse string to MPFloat."); 113 | Py_DECREF(self); 114 | return NULL; 115 | } 116 | } 117 | else { 118 | PyErr_SetString(PyExc_TypeError, 119 | "MPFloat value must be an MPF, float, or string"); 120 | Py_DECREF(self); 121 | return NULL; 122 | } 123 | 124 | return self; 125 | } 126 | 127 | 128 | static PyObject * 129 | MPFloat_new(PyTypeObject *cls, PyObject *args, PyObject *kwargs) 130 | { 131 | char *keywords[] = {"", "prec", NULL}; 132 | PyObject *value; 133 | Py_ssize_t prec = -1; /* default precision may be discovered */ 134 | 135 | if (!PyArg_ParseTupleAndKeywords( 136 | args, kwargs, "O|$n", keywords, &value, &prec)) { 137 | return NULL; 138 | } 139 | 140 | return (PyObject *)MPFloat_from_object(value, prec); 141 | } 142 | 143 | 144 | static PyObject * 145 | MPFloat_str(MPFloatObject* self) 146 | { 147 | char *val_repr; 148 | char format[100]; 149 | 150 | mpfr_prec_t precision = mpfr_min_prec(self->mpf.x); 151 | 152 | // TODO: I am not 100% sure this ensures round-tripping. 153 | size_t ndigits = mpfr_get_str_ndigits(10, precision); 154 | ndigits -= 1; 155 | if (ndigits < 0) { 156 | ndigits = 0; 157 | } 158 | snprintf(format, 99, "%%.%zdRE", ndigits); 159 | 160 | /* Note: For format characters other than "e" precision is needed */ 161 | if (mpfr_asprintf(&val_repr, format, self->mpf.x) < 0) { 162 | /* Lets assume errors must be memory errors. */ 163 | PyErr_NoMemory(); 164 | return NULL; 165 | } 166 | PyObject *str = PyUnicode_FromString(val_repr); 167 | mpfr_free_str(val_repr); 168 | return str; 169 | } 170 | 171 | 172 | static PyObject * 173 | MPFloat_repr(MPFloatObject* self) 174 | { 175 | PyObject *val_repr = MPFloat_str(self); 176 | if (val_repr == NULL) { 177 | return NULL; 178 | } 179 | 180 | PyObject *res = PyUnicode_FromFormat( 181 | "MPFloat('%S', prec=%zd)", val_repr, mpfr_get_prec(self->mpf.x)); 182 | Py_DECREF(val_repr); 183 | return res; 184 | } 185 | 186 | 187 | static PyObject * 188 | MPFloat_get_prec(MPFloatObject *self) 189 | { 190 | return PyLong_FromLong(mpfr_get_prec(self->mpf.x)); 191 | } 192 | 193 | 194 | NPY_NO_EXPORT PyGetSetDef mpfloat_getsetlist[] = { 195 | {"prec", 196 | (getter)MPFloat_get_prec, 197 | NULL, 198 | NULL, NULL}, 199 | {NULL, NULL, NULL, NULL, NULL}, /* Sentinel */ 200 | }; 201 | 202 | 203 | PyTypeObject MPFloat_Type = { 204 | PyVarObject_HEAD_INIT(NULL, 0) 205 | .tp_name = "MPFDType.MPFDType", 206 | .tp_basicsize = sizeof(MPFloatObject), 207 | .tp_itemsize = sizeof(mp_limb_t), 208 | .tp_new = MPFloat_new, 209 | .tp_repr = (reprfunc)MPFloat_repr, 210 | .tp_str = (reprfunc)MPFloat_str, 211 | .tp_as_number = &mpf_as_number, 212 | .tp_richcompare = (richcmpfunc)mpf_richcompare, 213 | .tp_getset = mpfloat_getsetlist, 214 | }; 215 | 216 | 217 | int 218 | init_mpf_scalar(void) 219 | { 220 | return PyType_Ready(&MPFloat_Type); 221 | } 222 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/src/quadblas_interface.cpp: -------------------------------------------------------------------------------- 1 | #include "quadblas_interface.h" 2 | #include 3 | #include 4 | 5 | #ifndef DISABLE_QUADBLAS 6 | #include "../subprojects/qblas/include/quadblas/quadblas.hpp" 7 | #endif // DISABLE_QUADBLAS 8 | 9 | extern "C" { 10 | 11 | 12 | #ifndef DISABLE_QUADBLAS 13 | 14 | int 15 | qblas_dot(size_t n, Sleef_quad *x, size_t incx, Sleef_quad *y, size_t incy, Sleef_quad *result) 16 | { 17 | if (!x || !y || !result || n == 0) { 18 | return -1; 19 | } 20 | 21 | try { 22 | *result = QuadBLAS::dot(n, x, incx, y, incy); 23 | return 0; 24 | } 25 | catch (...) { 26 | return -1; 27 | } 28 | } 29 | 30 | int 31 | qblas_gemv(char layout, char trans, size_t m, size_t n, Sleef_quad *alpha, Sleef_quad *A, 32 | size_t lda, Sleef_quad *x, size_t incx, Sleef_quad *beta, Sleef_quad *y, size_t incy) 33 | { 34 | if (!alpha || !A || !x || !beta || !y || m == 0 || n == 0) { 35 | return -1; 36 | } 37 | 38 | try { 39 | // Convert layout 40 | QuadBLAS::Layout qblas_layout; 41 | if (layout == 'R' || layout == 'r') { 42 | qblas_layout = QuadBLAS::Layout::RowMajor; 43 | } 44 | else if (layout == 'C' || layout == 'c') { 45 | qblas_layout = QuadBLAS::Layout::ColMajor; 46 | } 47 | else { 48 | return -1; // Invalid layout 49 | } 50 | 51 | // Handle transpose (swap dimensions for transpose) 52 | size_t actual_m = m, actual_n = n; 53 | if (trans == 'T' || trans == 't' || trans == 'C' || trans == 'c') { 54 | std::swap(actual_m, actual_n); 55 | // For transpose, we need to adjust the layout 56 | if (qblas_layout == QuadBLAS::Layout::RowMajor) { 57 | qblas_layout = QuadBLAS::Layout::ColMajor; 58 | } 59 | else { 60 | qblas_layout = QuadBLAS::Layout::RowMajor; 61 | } 62 | } 63 | 64 | // Call QBLAS GEMV 65 | QuadBLAS::gemv(qblas_layout, actual_m, actual_n, *alpha, A, lda, x, incx, *beta, y, incy); 66 | 67 | return 0; 68 | } 69 | catch (...) { 70 | return -1; 71 | } 72 | } 73 | 74 | int 75 | qblas_gemm(char layout, char transa, char transb, size_t m, size_t n, size_t k, Sleef_quad *alpha, 76 | Sleef_quad *A, size_t lda, Sleef_quad *B, size_t ldb, Sleef_quad *beta, Sleef_quad *C, 77 | size_t ldc) 78 | { 79 | if (!alpha || !A || !B || !beta || !C || m == 0 || n == 0 || k == 0) { 80 | return -1; 81 | } 82 | 83 | try { 84 | QuadBLAS::Layout qblas_layout; 85 | if (layout == 'R' || layout == 'r') { 86 | qblas_layout = QuadBLAS::Layout::RowMajor; 87 | } 88 | else if (layout == 'C' || layout == 'c') { 89 | qblas_layout = QuadBLAS::Layout::ColMajor; 90 | } 91 | else { 92 | return -1; // Invalid layout 93 | } 94 | 95 | // For now, we only support no transpose 96 | // TODO: Implement transpose support if needed 97 | if ((transa != 'N' && transa != 'n') || (transb != 'N' && transb != 'n')) { 98 | return -1; // Transpose not implemented yet 99 | } 100 | 101 | QuadBLAS::gemm(qblas_layout, m, n, k, *alpha, A, lda, B, ldb, *beta, C, ldc); 102 | 103 | return 0; 104 | } 105 | catch (...) { 106 | return -1; 107 | } 108 | } 109 | 110 | int 111 | qblas_supports_backend(QuadBackendType backend) 112 | { 113 | // QBLAS only supports SLEEF backend 114 | return (backend == BACKEND_SLEEF) ? 1 : 0; 115 | } 116 | 117 | PyObject * 118 | py_quadblas_set_num_threads(PyObject *self, PyObject *args) 119 | { 120 | int num_threads; 121 | if (!PyArg_ParseTuple(args, "i", &num_threads)) { 122 | return NULL; 123 | } 124 | 125 | if (num_threads <= 0) { 126 | PyErr_SetString(PyExc_ValueError, "Number of threads must be positive"); 127 | return NULL; 128 | } 129 | 130 | QuadBLAS::set_num_threads(num_threads); 131 | Py_RETURN_NONE; 132 | } 133 | 134 | PyObject * 135 | py_quadblas_get_num_threads(PyObject *self, PyObject *args) 136 | { 137 | int num_threads = QuadBLAS::get_num_threads(); 138 | return PyLong_FromLong(num_threads); 139 | } 140 | 141 | PyObject * 142 | py_quadblas_get_version(PyObject *self, PyObject *args) 143 | { 144 | return PyUnicode_FromString("QuadBLAS 1.0.0 - High Performance Quad Precision BLAS"); 145 | } 146 | 147 | int 148 | _quadblas_set_num_threads(int num_threads) 149 | { 150 | QuadBLAS::set_num_threads(num_threads); 151 | return 0; 152 | } 153 | 154 | int 155 | _quadblas_get_num_threads(void) 156 | { 157 | int num_threads = QuadBLAS::get_num_threads(); 158 | return num_threads; 159 | } 160 | 161 | #else // DISABLE_QUADBLAS 162 | 163 | 164 | int 165 | qblas_dot(size_t n, Sleef_quad *x, size_t incx, Sleef_quad *y, size_t incy, Sleef_quad *result) 166 | { 167 | return -1; // QBLAS is disabled, dot product not available 168 | } 169 | 170 | int 171 | qblas_gemv(char layout, char trans, size_t m, size_t n, Sleef_quad *alpha, Sleef_quad *A, 172 | size_t lda, Sleef_quad *x, size_t incx, Sleef_quad *beta, Sleef_quad *y, size_t incy) 173 | { 174 | return -1; // QBLAS is disabled, GEMV not available 175 | } 176 | 177 | int 178 | qblas_gemm(char layout, char transa, char transb, size_t m, size_t n, size_t k, Sleef_quad *alpha, 179 | Sleef_quad *A, size_t lda, Sleef_quad *B, size_t ldb, Sleef_quad *beta, Sleef_quad *C, 180 | size_t ldc) 181 | { 182 | return -1; // QBLAS is disabled, GEMM not available 183 | } 184 | 185 | int 186 | qblas_supports_backend(QuadBackendType backend) 187 | { 188 | return -1; // QBLAS is disabled, backend support not available 189 | } 190 | 191 | PyObject * 192 | py_quadblas_set_num_threads(PyObject *self, PyObject *args) 193 | { 194 | // raise error 195 | PyErr_SetString(PyExc_NotImplementedError, "QuadBLAS is disabled"); 196 | return NULL; 197 | } 198 | 199 | PyObject * 200 | py_quadblas_get_num_threads(PyObject *self, PyObject *args) 201 | { 202 | // raise error 203 | PyErr_SetString(PyExc_NotImplementedError, "QuadBLAS is disabled"); 204 | return NULL; 205 | } 206 | 207 | PyObject * 208 | py_quadblas_get_version(PyObject *self, PyObject *args) 209 | { 210 | // raise error 211 | PyErr_SetString(PyExc_NotImplementedError, "QuadBLAS is disabled"); 212 | return NULL; 213 | } 214 | 215 | int 216 | _quadblas_set_num_threads(int num_threads) 217 | { 218 | // raise error 219 | PyErr_SetString(PyExc_NotImplementedError, "QuadBLAS is disabled"); 220 | return -1; 221 | } 222 | 223 | int 224 | _quadblas_get_num_threads(void) 225 | { 226 | // raise error 227 | PyErr_SetString(PyExc_NotImplementedError, "QuadBLAS is disabled"); 228 | return -1; 229 | } 230 | 231 | #endif // DISABLE_QUADBLAS 232 | 233 | } // extern "C" -------------------------------------------------------------------------------- /.github/workflows/big_endian.yml: -------------------------------------------------------------------------------- 1 | name: Big-Endian Architecture Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | paths: 8 | - "quaddtype/**" 9 | - ".github/workflows/**" 10 | workflow_dispatch: 11 | 12 | defaults: 13 | run: 14 | shell: bash 15 | 16 | concurrency: 17 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 18 | cancel-in-progress: true 19 | 20 | permissions: 21 | contents: read 22 | 23 | jobs: 24 | big_endian_tests: 25 | runs-on: ubuntu-22.04 26 | continue-on-error: true 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | BUILD_PROP: 31 | - [ 32 | "s390x (IBM Z Big Endian)", 33 | "s390x-linux-gnu", 34 | "s390x/ubuntu:24.04", 35 | "s390x", 36 | ] 37 | - [ 38 | "s390x - baseline(Z13)", 39 | "s390x-linux-gnu", 40 | "s390x/ubuntu:24.04", 41 | "s390x", 42 | ] 43 | env: 44 | ARCH_NAME: ${{ matrix.BUILD_PROP[0] }} 45 | TOOLCHAIN_NAME: ${{ matrix.BUILD_PROP[1] }} 46 | DOCKER_CONTAINER: ${{ matrix.BUILD_PROP[2] }} 47 | ARCH: ${{ matrix.BUILD_PROP[3] }} 48 | TERM: xterm-256color 49 | 50 | name: "${{ matrix.BUILD_PROP[0] }}" 51 | steps: 52 | - uses: actions/checkout@v6 53 | with: 54 | submodules: recursive 55 | fetch-tags: true 56 | persist-credentials: false 57 | 58 | - name: Initialize binfmt_misc for qemu-user-static 59 | run: | 60 | # Enable QEMU user-mode emulation for cross-architecture execution 61 | docker run --rm --privileged tonistiigi/binfmt:qemu-v9.2.2-52 --install all 62 | 63 | - name: Install cross-compilation toolchain 64 | run: | 65 | sudo apt update 66 | sudo apt install -y ninja-build gcc-${TOOLCHAIN_NAME} g++-${TOOLCHAIN_NAME} gfortran-${TOOLCHAIN_NAME} 67 | 68 | - name: Cache docker container 69 | uses: actions/cache@v4 70 | id: container-cache 71 | with: 72 | path: ~/docker_${{ matrix.BUILD_PROP[1] }} 73 | key: container-quaddtype-${{ runner.os }}-${{ matrix.BUILD_PROP[1] }}-${{ matrix.BUILD_PROP[2] }}-${{ hashFiles('quaddtype/pyproject.toml') }} 74 | 75 | - name: Create cross-compilation container 76 | if: steps.container-cache.outputs.cache-hit != 'true' 77 | run: | 78 | docker run --platform=linux/${ARCH} --name quaddtype_container --interactive \ 79 | -v /:/host -v $(pwd):/workspace ${DOCKER_CONTAINER} /bin/bash -c " 80 | # Update package manager and install essential tools 81 | apt update && 82 | apt install -y cmake git python3 python-is-python3 python3-dev python3-pip build-essential && 83 | 84 | # Create necessary symlinks for cross-compilation 85 | mkdir -p /lib64 && ln -sf /host/lib64/ld-* /lib64/ || true && 86 | ln -sf /host/lib/x86_64-linux-gnu /lib/x86_64-linux-gnu || true && 87 | 88 | # Link cross-compilation toolchain from host 89 | rm -rf /usr/${TOOLCHAIN_NAME} && ln -sf /host/usr/${TOOLCHAIN_NAME} /usr/${TOOLCHAIN_NAME} && 90 | rm -rf /usr/lib/gcc/${TOOLCHAIN_NAME} && ln -sf /host/usr/lib/gcc-cross/${TOOLCHAIN_NAME} /usr/lib/gcc/${TOOLCHAIN_NAME} && 91 | 92 | # Set up compiler symlinks 93 | rm -f /usr/bin/gcc && ln -sf /host/usr/bin/${TOOLCHAIN_NAME}-gcc /usr/bin/gcc && 94 | rm -f /usr/bin/g++ && ln -sf /host/usr/bin/${TOOLCHAIN_NAME}-g++ /usr/bin/g++ && 95 | rm -f /usr/bin/gfortran && ln -sf /host/usr/bin/${TOOLCHAIN_NAME}-gfortran /usr/bin/gfortran && 96 | 97 | # Set up binutils 98 | rm -f /usr/bin/ar && ln -sf /host/usr/bin/${TOOLCHAIN_NAME}-ar /usr/bin/ar && 99 | rm -f /usr/bin/as && ln -sf /host/usr/bin/${TOOLCHAIN_NAME}-as /usr/bin/as && 100 | rm -f /usr/bin/ld && ln -sf /host/usr/bin/${TOOLCHAIN_NAME}-ld /usr/bin/ld && 101 | rm -f /usr/bin/ld.bfd && ln -sf /host/usr/bin/${TOOLCHAIN_NAME}-ld.bfd /usr/bin/ld.bfd && 102 | 103 | # Link build tools 104 | rm -f /usr/bin/ninja && ln -sf /host/usr/bin/ninja /usr/bin/ninja && 105 | rm -f /usr/local/bin/ninja && mkdir -p /usr/local/bin && ln -sf /host/usr/bin/ninja /usr/local/bin/ninja && 106 | 107 | # Configure git for workspace access 108 | git config --global --add safe.directory /workspace && 109 | 110 | # Install Python build dependencies (using --break-system-packages for Ubuntu 24.04) 111 | python -m pip install --break-system-packages meson>=1.3.2 meson-python wheel ninja cython && 112 | echo \"Building NumPy from source (main branch)...\" && 113 | python -m pip install --break-system-packages git+https://github.com/numpy/numpy.git@main && 114 | python -c \"import numpy; print('NumPy version:', numpy.__version__)\" && 115 | python -m pip install --break-system-packages pytest pytest-run-parallel pytest-timeout && 116 | 117 | # Install system dependencies for quaddtype (SLEEF dependencies) 118 | apt install -y libssl-dev libfftw3-dev pkg-config 119 | " 120 | docker commit quaddtype_container quaddtype_container 121 | mkdir -p "~/docker_${TOOLCHAIN_NAME}" 122 | docker save -o "~/docker_${TOOLCHAIN_NAME}/quaddtype_container.tar" quaddtype_container 123 | 124 | - name: Load container from cache 125 | if: steps.container-cache.outputs.cache-hit == 'true' 126 | run: docker load -i "~/docker_${TOOLCHAIN_NAME}/quaddtype_container.tar" 127 | 128 | - name: Build quaddtype with cross-compilation and testing 129 | run: | 130 | docker run --rm --platform=linux/${ARCH} -e "TERM=xterm-256color" \ 131 | -v $(pwd):/workspace -v /:/host quaddtype_container \ 132 | /bin/script -e -q -c "/bin/bash --noprofile --norc -eo pipefail -c ' 133 | cd /workspace/quaddtype && 134 | echo \"Building quaddtype for ${ARCH_NAME}...\" && 135 | 136 | # Set OpenMP linking for cross-compilation 137 | export LDFLAGS=\"-fopenmp\" && 138 | 139 | # Install quaddtype without dependencies (NumPy already installed from source) 140 | python -m pip install --break-system-packages --no-deps . -v --no-build-isolation --force-reinstall && 141 | 142 | # Install test dependencies separately 143 | python -m pip install --break-system-packages pytest pytest-run-parallel pytest-timeout mpmath && 144 | 145 | cd .. 146 | python -m pytest -vvv --color=yes --timeout=600 --tb=short quaddtype/tests/ 147 | '" 148 | -------------------------------------------------------------------------------- /quaddtype/meson.build: -------------------------------------------------------------------------------- 1 | project('numpy_quaddtype', 'c', 'cpp', default_options : ['cpp_std=c++20', 'b_pie=true']) 2 | 3 | py_mod = import('python') 4 | py = py_mod.find_installation() 5 | py_dep = py.dependency() 6 | 7 | c = meson.get_compiler('c') 8 | cpp = meson.get_compiler('cpp') 9 | 10 | is_windows = build_machine.system() == 'windows' 11 | 12 | if is_windows 13 | add_project_arguments('-DWIN32', '-D_WINDOWS', language : ['c', 'cpp']) 14 | endif 15 | 16 | qblas_dep = dependency('qblas', fallback: ['qblas', 'qblas_dep']) 17 | 18 | # Try to find SLEEF system-wide first, fall back to subproject if not found 19 | sleef_dep = dependency('sleef', fallback: ['sleef', 'sleef_dep'], required: false) 20 | 21 | use_system_sleef = false 22 | fallback_reason = '' 23 | 24 | if sleef_dep.found() and sleef_dep.type_name() != 'internal' 25 | # SLEEF found system-wide - verify quad-precision support 26 | cpp = meson.get_compiler('cpp') 27 | sleefquad_lib = cpp.find_library('sleefquad', required: false) 28 | 29 | if sleefquad_lib.found() 30 | sleefquad_test_code = ''' 31 | #include 32 | 33 | int main(void) { 34 | Sleef_quad q1 = Sleef_cast_from_doubleq1(1.0); 35 | Sleef_quad q2 = Sleef_cast_from_doubleq1(2.0); 36 | Sleef_quad result = Sleef_addq1_u05(q1, q2); 37 | return 0; 38 | } 39 | ''' 40 | # this should compile and link 41 | quad_works = cpp.links( 42 | sleefquad_test_code, 43 | dependencies: [sleef_dep, sleefquad_lib], 44 | name: 'SLEEF quad-precision support' 45 | ) 46 | 47 | if quad_works 48 | sleefquad_dep = declare_dependency( 49 | dependencies: [sleef_dep, sleefquad_lib] 50 | ) 51 | use_system_sleef = true 52 | else 53 | fallback_reason = 'System-wide SLEEF installation found but a test for quad precision support failed.' 54 | endif 55 | else 56 | fallback_reason = 'System-wide SLEEF installation does not have a sleefquad library.' 57 | endif 58 | else 59 | fallback_reason = 'Cannot find system-wide SLEEF installation.' 60 | endif 61 | 62 | if use_system_sleef 63 | message('Using system-wide SLEEF installation with quad-precision support') 64 | else 65 | sleef_subproj = subproject('sleef') 66 | sleef_dep = sleef_subproj.get_variable('sleef_dep') 67 | sleefquad_dep = sleef_subproj.get_variable('sleefquad_dep') 68 | warning(fallback_reason) 69 | message('Proceeding with vendored SLEEF subproject instead') 70 | endif 71 | 72 | incdir_numpy = run_command(py, 73 | ['-c', 'import numpy; print(numpy.get_include())'], 74 | check : true 75 | ).stdout().strip() 76 | 77 | # print numpy version used 78 | numpy_version = run_command(py, 79 | ['-c', 'import numpy; print(numpy.__version__)'], 80 | check : true 81 | ).stdout().strip() 82 | message('Using NumPy version: @0@'.format(numpy_version)) 83 | 84 | npymath_path = incdir_numpy / '..' / 'lib' 85 | npymath_lib = c.find_library('npymath', dirs: npymath_path) 86 | 87 | dependencies = [py_dep, qblas_dep, sleef_dep, sleefquad_dep, npymath_lib] 88 | 89 | # Add OpenMP dependency (optional, for threading) 90 | openmp_dep = dependency('openmp', required: false, static: false) 91 | if openmp_dep.found() 92 | dependencies += openmp_dep 93 | endif 94 | 95 | # compiler flags for QBLAS compatibility 96 | if not is_windows 97 | # QBLAS requires extended numeric literals for Q suffix support 98 | # if compiler supports (usually gcc) 99 | if cpp.has_argument('-fext-numeric-literals') 100 | add_project_arguments('-fext-numeric-literals', language: 'cpp') 101 | endif 102 | endif 103 | 104 | # Thread-local storage detection (borrowed from NumPy) 105 | optional_variable_attributes = [ 106 | ['thread_local', 'HAVE_THREAD_LOCAL'], # C23 107 | ['_Thread_local', 'HAVE__THREAD_LOCAL'], # C11/C17 108 | ['__thread', 'HAVE___THREAD'], # GCC/Clang 109 | ['__declspec(thread)', 'HAVE___DECLSPEC_THREAD_'] # MSVC 110 | ] 111 | 112 | if not is_variable('cdata') 113 | cdata = configuration_data() 114 | endif 115 | 116 | foreach optional_attr: optional_variable_attributes 117 | attr = optional_attr[0] 118 | code = ''' 119 | #pragma GCC diagnostic error "-Wattributes" 120 | #pragma clang diagnostic error "-Wattributes" 121 | 122 | int @0@ foo; 123 | 124 | int main() { 125 | return 0; 126 | } 127 | '''.format(attr) 128 | 129 | if c.compiles(code, name: optional_attr[0]) 130 | cdata.set10(optional_attr[1], true) 131 | message('Thread-local storage support found: @0@'.format(attr)) 132 | endif 133 | endforeach 134 | 135 | configure_file( 136 | output: 'quaddtype_config.h', 137 | configuration: cdata 138 | ) 139 | 140 | build_includes = include_directories('.') # compile time generated headers as per system 141 | includes = include_directories( 142 | [ 143 | incdir_numpy, 144 | 'numpy_quaddtype/src', 145 | ] 146 | ) 147 | 148 | srcs = [ 149 | 'numpy_quaddtype/src/quad_common.h', 150 | 'numpy_quaddtype/src/casts.h', 151 | 'numpy_quaddtype/src/casts.cpp', 152 | 'numpy_quaddtype/src/scalar.h', 153 | 'numpy_quaddtype/src/scalar.c', 154 | 'numpy_quaddtype/src/dtype.h', 155 | 'numpy_quaddtype/src/dtype.c', 156 | 'numpy_quaddtype/src/quaddtype_main.c', 157 | 'numpy_quaddtype/src/scalar_ops.h', 158 | 'numpy_quaddtype/src/scalar_ops.cpp', 159 | 'numpy_quaddtype/src/ops.hpp', 160 | 'numpy_quaddtype/src/dragon4.h', 161 | 'numpy_quaddtype/src/dragon4.c', 162 | 'numpy_quaddtype/src/quadblas_interface.h', 163 | 'numpy_quaddtype/src/quadblas_interface.cpp', 164 | 'numpy_quaddtype/src/umath/umath.h', 165 | 'numpy_quaddtype/src/umath/umath.cpp', 166 | 'numpy_quaddtype/src/umath/binary_ops.h', 167 | 'numpy_quaddtype/src/umath/binary_ops.cpp', 168 | 'numpy_quaddtype/src/umath/unary_ops.h', 169 | 'numpy_quaddtype/src/umath/unary_ops.cpp', 170 | 'numpy_quaddtype/src/umath/unary_props.h', 171 | 'numpy_quaddtype/src/umath/unary_props.cpp', 172 | 'numpy_quaddtype/src/umath/comparison_ops.h', 173 | 'numpy_quaddtype/src/umath/comparison_ops.cpp', 174 | 'numpy_quaddtype/src/umath/promoters.hpp', 175 | 'numpy_quaddtype/src/umath/matmul.h', 176 | 'numpy_quaddtype/src/umath/matmul.cpp', 177 | 'numpy_quaddtype/src/constants.hpp', 178 | 'numpy_quaddtype/src/lock.h', 179 | 'numpy_quaddtype/src/lock.c', 180 | 'numpy_quaddtype/src/utilities.h', 181 | 'numpy_quaddtype/src/utilities.c', 182 | ] 183 | 184 | py.install_sources( 185 | [ 186 | 'numpy_quaddtype/__init__.py', 187 | 'numpy_quaddtype/__init__.pyi', 188 | 'numpy_quaddtype/_quaddtype_main.pyi', 189 | 'numpy_quaddtype/py.typed', 190 | ], 191 | subdir: 'numpy_quaddtype', 192 | pure: false 193 | ) 194 | 195 | py.extension_module('_quaddtype_main', 196 | srcs, 197 | link_language: 'cpp', 198 | dependencies: dependencies, 199 | install: true, 200 | subdir: 'numpy_quaddtype', 201 | include_directories: [includes, build_includes], 202 | ) 203 | -------------------------------------------------------------------------------- /asciidtype/asciidtype/src/umath.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define PY_ARRAY_UNIQUE_SYMBOL asciidtype_ARRAY_API 4 | #define PY_UFUNC_UNIQUE_SYMBOL asciidtype_UFUNC_API 5 | #define NPY_NO_DEPRECATED_API NPY_2_0_API_VERSION 6 | #define NPY_TARGET_VERSION NPY_2_0_API_VERSION 7 | #define NO_IMPORT_ARRAY 8 | #define NO_IMPORT_UFUNC 9 | #include "numpy/ndarraytypes.h" 10 | #include "numpy/arrayobject.h" 11 | #include "numpy/ufuncobject.h" 12 | #include "numpy/dtype_api.h" 13 | 14 | #include "dtype.h" 15 | #include "string.h" 16 | #include "umath.h" 17 | 18 | static int 19 | ascii_add_strided_loop(PyArrayMethod_Context *context, char *const data[], 20 | npy_intp const dimensions[], npy_intp const strides[], 21 | NpyAuxData *NPY_UNUSED(auxdata)) 22 | { 23 | PyArray_Descr *const *descrs = context->descriptors; 24 | long in1_size = ((ASCIIDTypeObject *)descrs[0])->size; 25 | long in2_size = ((ASCIIDTypeObject *)descrs[1])->size; 26 | long out_size = ((ASCIIDTypeObject *)descrs[2])->size; 27 | 28 | npy_intp N = dimensions[0]; 29 | char *in1 = data[0], *in2 = data[1], *out = data[2]; 30 | npy_intp in1_stride = strides[0], in2_stride = strides[1], 31 | out_stride = strides[2]; 32 | 33 | while (N--) { 34 | size_t in1_len = strnlen(in1, in1_size); 35 | size_t in2_len = strnlen(in2, in2_size); 36 | strncpy(out, in1, in1_len); 37 | strncpy(out + in1_len, in2, in2_len); 38 | if (in1_len + in2_len < out_size) { 39 | out[in1_len + in2_len] = '\0'; 40 | } 41 | in1 += in1_stride; 42 | in2 += in2_stride; 43 | out += out_stride; 44 | } 45 | 46 | return 0; 47 | } 48 | 49 | static NPY_CASTING 50 | ascii_add_resolve_descriptors(PyObject *NPY_UNUSED(self), 51 | PyArray_DTypeMeta *dtypes[], 52 | PyArray_Descr *given_descrs[], 53 | PyArray_Descr *loop_descrs[], 54 | npy_intp *NPY_UNUSED(view_offset)) 55 | { 56 | long op1_size = ((ASCIIDTypeObject *)given_descrs[0])->size; 57 | long op2_size = ((ASCIIDTypeObject *)given_descrs[1])->size; 58 | long out_size = op1_size + op2_size; 59 | 60 | /* the input descriptors can be used as-is */ 61 | Py_INCREF(given_descrs[0]); 62 | loop_descrs[0] = given_descrs[0]; 63 | Py_INCREF(given_descrs[1]); 64 | loop_descrs[1] = given_descrs[1]; 65 | 66 | /* create new DType instance to hold the output */ 67 | loop_descrs[2] = (PyArray_Descr *)new_asciidtype_instance(out_size); 68 | if (loop_descrs[2] == NULL) { 69 | return -1; 70 | } 71 | 72 | return NPY_SAFE_CASTING; 73 | } 74 | 75 | int 76 | init_add_ufunc(PyObject *numpy) 77 | { 78 | PyObject *add = PyObject_GetAttrString(numpy, "add"); 79 | if (add == NULL) { 80 | return -1; 81 | } 82 | 83 | /* 84 | * Initialize spec for addition 85 | */ 86 | static PyArray_DTypeMeta *add_dtypes[3] = {&ASCIIDType, &ASCIIDType, 87 | &ASCIIDType}; 88 | 89 | static PyType_Slot add_slots[] = { 90 | {NPY_METH_resolve_descriptors, &ascii_add_resolve_descriptors}, 91 | {NPY_METH_strided_loop, &ascii_add_strided_loop}, 92 | {0, NULL}}; 93 | 94 | PyArrayMethod_Spec AddSpec = { 95 | .name = "ascii_add", 96 | .nin = 2, 97 | .nout = 1, 98 | .dtypes = add_dtypes, 99 | .slots = add_slots, 100 | .flags = 0, 101 | .casting = NPY_SAFE_CASTING, 102 | }; 103 | 104 | /* register ufunc */ 105 | if (PyUFunc_AddLoopFromSpec(add, &AddSpec) < 0) { 106 | Py_DECREF(add); 107 | return -1; 108 | } 109 | Py_DECREF(add); 110 | return 0; 111 | } 112 | 113 | static int 114 | ascii_equal_strided_loop(PyArrayMethod_Context *context, char *const data[], 115 | npy_intp const dimensions[], npy_intp const strides[], 116 | NpyAuxData *NPY_UNUSED(auxdata)) 117 | { 118 | PyArray_Descr *const *descrs = context->descriptors; 119 | long in1_size = ((ASCIIDTypeObject *)descrs[0])->size; 120 | long in2_size = ((ASCIIDTypeObject *)descrs[1])->size; 121 | 122 | npy_intp N = dimensions[0]; 123 | char *in1 = data[0], *in2 = data[1]; 124 | npy_bool *out = (npy_bool *)data[2]; 125 | npy_intp in1_stride = strides[0], in2_stride = strides[1], 126 | out_stride = strides[2]; 127 | 128 | while (N--) { 129 | *out = (npy_bool)1; 130 | char *_in1 = in1; 131 | char *_in2 = in2; 132 | npy_bool *_out = out; 133 | in1 += in1_stride; 134 | in2 += in2_stride; 135 | out += out_stride; 136 | if (in1_size > in2_size) { 137 | if (_in1[in2_size] != '\0') { 138 | *_out = (npy_bool)0; 139 | continue; 140 | } 141 | if (strncmp(_in1, _in2, in2_size) != 0) { 142 | *_out = (npy_bool)0; 143 | } 144 | } 145 | else { 146 | if (in2_size > in1_size) { 147 | if (_in2[in1_size] != '\0') { 148 | *_out = (npy_bool)0; 149 | continue; 150 | } 151 | } 152 | if (strncmp(_in1, _in2, in1_size) != 0) { 153 | *_out = (npy_bool)0; 154 | } 155 | } 156 | } 157 | 158 | return 0; 159 | } 160 | 161 | static NPY_CASTING 162 | ascii_equal_resolve_descriptors(PyObject *NPY_UNUSED(self), 163 | PyArray_DTypeMeta *dtypes[], 164 | PyArray_Descr *given_descrs[], 165 | PyArray_Descr *loop_descrs[], 166 | npy_intp *NPY_UNUSED(view_offset)) 167 | { 168 | Py_INCREF(given_descrs[0]); 169 | loop_descrs[0] = given_descrs[0]; 170 | Py_INCREF(given_descrs[1]); 171 | loop_descrs[1] = given_descrs[1]; 172 | 173 | loop_descrs[2] = PyArray_DescrFromType(NPY_BOOL); // cannot fail 174 | 175 | return NPY_SAFE_CASTING; 176 | } 177 | 178 | static char *equal_name = "ascii_equal"; 179 | 180 | int 181 | init_equal_ufunc(PyObject *numpy) 182 | { 183 | PyObject *equal = PyObject_GetAttrString(numpy, "equal"); 184 | if (equal == NULL) { 185 | return -1; 186 | } 187 | 188 | /* 189 | * Initialize spec for equality 190 | */ 191 | PyArray_DTypeMeta **eq_dtypes = malloc(3 * sizeof(PyArray_DTypeMeta *)); 192 | eq_dtypes[0] = &ASCIIDType; 193 | eq_dtypes[1] = &ASCIIDType; 194 | eq_dtypes[2] = &PyArray_BoolDType; 195 | 196 | static PyType_Slot eq_slots[] = { 197 | {NPY_METH_resolve_descriptors, &ascii_equal_resolve_descriptors}, 198 | {NPY_METH_strided_loop, &ascii_equal_strided_loop}, 199 | {0, NULL}}; 200 | 201 | PyArrayMethod_Spec *EqualSpec = malloc(sizeof(PyArrayMethod_Spec)); 202 | 203 | EqualSpec->name = equal_name; 204 | EqualSpec->nin = 2; 205 | EqualSpec->nout = 1; 206 | EqualSpec->casting = NPY_SAFE_CASTING; 207 | EqualSpec->flags = 0; 208 | EqualSpec->dtypes = eq_dtypes; 209 | EqualSpec->slots = eq_slots; 210 | 211 | if (PyUFunc_AddLoopFromSpec(equal, EqualSpec) < 0) { 212 | Py_DECREF(equal); 213 | free(eq_dtypes); 214 | free(EqualSpec); 215 | return -1; 216 | } 217 | 218 | Py_DECREF(equal); 219 | free(eq_dtypes); 220 | free(EqualSpec); 221 | return 0; 222 | } 223 | 224 | int 225 | init_ufuncs(void) 226 | { 227 | PyObject *numpy = PyImport_ImportModule("numpy"); 228 | if (numpy == NULL) { 229 | return -1; 230 | } 231 | 232 | if (init_add_ufunc(numpy) < 0) { 233 | goto error; 234 | } 235 | 236 | if (init_equal_ufunc(numpy) < 0) { 237 | goto error; 238 | } 239 | 240 | return 0; 241 | 242 | error: 243 | Py_DECREF(numpy); 244 | return -1; 245 | } 246 | -------------------------------------------------------------------------------- /quaddtype/numpy_quaddtype/_quaddtype_main.pyi: -------------------------------------------------------------------------------- 1 | from typing import Any, Literal, TypeAlias, final, overload 2 | import builtins 3 | import numpy as np 4 | from numpy._typing import _128Bit # pyright: ignore[reportPrivateUsage] 5 | from typing_extensions import Never, Self, override 6 | 7 | from numpy_quaddtype import QuadBackend 8 | 9 | _Backend: TypeAlias = Literal["sleef", "longdouble"] 10 | _IntoQuad: TypeAlias = ( 11 | QuadPrecision 12 | | float 13 | | str 14 | | bytes 15 | | np.floating[Any] 16 | | np.integer[Any] 17 | | np.bool_ 18 | ) # fmt: skip 19 | _ScalarItemArg: TypeAlias = Literal[0, -1] | tuple[Literal[0, -1]] | tuple[()] 20 | 21 | @final 22 | class QuadPrecDType(np.dtype[QuadPrecision]): # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] 23 | def __new__(cls, /, backend: _Backend = "sleef") -> Self: ... 24 | 25 | # QuadPrecDType specific attributes 26 | @property 27 | def backend(self) -> QuadBackend: ... 28 | 29 | # `numpy.dtype` overrides 30 | names: None # pyright: ignore[reportIncompatibleVariableOverride] 31 | @property 32 | @override 33 | def alignment(self) -> Literal[16]: ... 34 | @property 35 | @override 36 | def itemsize(self) -> Literal[16]: ... 37 | @property 38 | @override 39 | def name(self) -> Literal["QuadPrecDType128"]: ... 40 | @property 41 | @override 42 | def byteorder(self) -> Literal["|"]: ... 43 | @property 44 | @override 45 | def char(self) -> Literal["\x00"]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 46 | @property 47 | @override 48 | def kind(self) -> Literal["\x00"]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 49 | @property 50 | @override 51 | def num(self) -> Literal[-1]: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 52 | @property 53 | @override 54 | def shape(self) -> tuple[()]: ... 55 | @property 56 | @override 57 | def ndim(self) -> Literal[0]: ... 58 | @property 59 | @override 60 | def fields(self) -> None: ... 61 | @property 62 | @override 63 | def base(self) -> Self: ... 64 | @property 65 | @override 66 | def subdtype(self) -> None: ... 67 | @property 68 | @override 69 | def hasobject(self) -> Literal[False]: ... 70 | @property 71 | @override 72 | def isbuiltin(self) -> Literal[0]: ... 73 | @property 74 | @override 75 | def isnative(self) -> Literal[True]: ... 76 | @property 77 | @override 78 | def isalignedstruct(self) -> Literal[False]: ... 79 | @override 80 | def __getitem__(self, key: Never, /) -> Self: ... # type: ignore[override] 81 | 82 | @final 83 | class QuadPrecision(np.floating[_128Bit]): 84 | # NOTE: At runtime this constructor also accepts array-likes, for which it returns 85 | # `np.ndarray` instances with `dtype=QuadPrecDType()`. 86 | # But because of mypy limitations, it is currently impossible to annotate 87 | # constructors that do not return instances of their class (or a subclass thereof). 88 | # See https://github.com/python/mypy/issues/18343#issuecomment-2571784915 89 | @override 90 | def __new__(cls, /, value: _IntoQuad, backend: _Backend = "sleef") -> Self: ... 91 | 92 | # numpy.floating property overrides 93 | 94 | @property 95 | @override 96 | def dtype(self) -> QuadPrecDType: ... 97 | @property 98 | @override 99 | def real(self) -> Self: ... 100 | @property 101 | @override 102 | def imag(self) -> Self: ... 103 | 104 | # numpy.floating method overrides 105 | 106 | @override 107 | def item(self, arg0: _ScalarItemArg = ..., /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 108 | @override 109 | def tolist(self, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 110 | 111 | # Equality operators 112 | @override 113 | def __eq__(self, other: object, /) -> bool: ... 114 | @override 115 | def __ne__(self, other: object, /) -> bool: ... 116 | 117 | # Rich comparison operators 118 | # NOTE: Unlike other numpy scalars, these return `builtins.bool`, not `np.bool`. 119 | @override 120 | def __lt__(self, other: _IntoQuad, /) -> bool: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 121 | @override 122 | def __le__(self, other: _IntoQuad, /) -> bool: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 123 | @override 124 | def __gt__(self, other: _IntoQuad, /) -> bool: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 125 | @override 126 | def __ge__(self, other: _IntoQuad, /) -> bool: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 127 | 128 | # Binary arithmetic operators 129 | @override 130 | def __add__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 131 | @override 132 | def __radd__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 133 | @override 134 | def __sub__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 135 | @override 136 | def __rsub__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 137 | @override 138 | def __mul__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 139 | @override 140 | def __rmul__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 141 | @override 142 | def __pow__(self, other: _IntoQuad, mod: None = None, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 143 | @override 144 | def __rpow__(self, other: _IntoQuad, mod: None = None, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 145 | @override 146 | def __truediv__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 147 | @override 148 | def __rtruediv__(self, other: _IntoQuad, /) -> Self: ... # type: ignore[override] # pyright: ignore[reportIncompatibleMethodOverride] 149 | 150 | # Binary modulo operators 151 | @override 152 | def __floordiv__(self, other: _IntoQuad, /) -> Self: ... 153 | @override 154 | def __rfloordiv__(self, other: _IntoQuad, /) -> Self: ... 155 | @override 156 | def __mod__(self, other: _IntoQuad, /) -> Self: ... 157 | @override 158 | def __rmod__(self, other: _IntoQuad, /) -> Self: ... 159 | @override 160 | def __divmod__(self, other: _IntoQuad, /) -> tuple[Self, Self]: ... 161 | @override 162 | def __rdivmod__(self, other: _IntoQuad, /) -> tuple[Self, Self]: ... 163 | 164 | # NOTE: is_integer() and as_integer_ratio() are defined on numpy.floating in the 165 | # stubs, but don't exist at runtime. And because QuadPrecision does not implement 166 | # them, we use this hacky workaround to emulate their absence. 167 | @override 168 | def is_integer(self, /) -> builtins.bool: ... 169 | @override 170 | def as_integer_ratio(self, /) -> tuple[int, int]: ... 171 | 172 | # 173 | def is_longdouble_128() -> bool: ... 174 | 175 | @overload 176 | def get_sleef_constant(constant_name: Literal["bits", "precision"], /) -> int: ... 177 | @overload 178 | def get_sleef_constant( 179 | constant_name: Literal[ 180 | "pi", 181 | "e", 182 | "log2e", 183 | "log10e", 184 | "ln2", 185 | "ln10", 186 | "max_value", 187 | "epsilon", 188 | "smallest_normal", 189 | "smallest_subnormal", 190 | "resolution", 191 | ], 192 | /, 193 | ) -> QuadPrecision: ... 194 | 195 | def set_num_threads(num_threads: int, /) -> None: ... 196 | def get_num_threads() -> int: ... 197 | def get_quadblas_version() -> str: ... 198 | --------------------------------------------------------------------------------