# -*- coding: utf-8 -*-
"""
Basic types
===========
The basic or main **types provided by the package are mainly those used in
numerical computation**, such as the types covered by the
:code:`iso_fortran_env` intrinsic module introduced by the 2003 Fortran
standard. Other **C types provided by the ctypes module still work with the
rest of the API**, so they can be imported from there and used if necessary.
See
`here <https://docs.python.org/3/library/ctypes.html#fundamental-data-types>`_.
.. admonition:: Available types
:class: hint
:Integer:
*Signed* :py:func:`16-bit<FancyShortInteger>` -
:py:func:`32-bit<FancyInteger>` - :py:func:`64-bit<FancyLongInteger>` /
*Unsigned* :py:func:`16-bit<FancyUnsignedShortInteger>` -
:py:func:`32-bit<FancyUnsignedInteger>` -
:py:func:`64-bit<FancyUnsignedLongInteger>`
:Real/Float:
:py:func:`32-bit<FancyRealSingle>` - :py:func:`64-bit<FancyRealDouble>`
:Complex:
:py:func:`64-bit<FancyComplexSingle>` -
:py:func:`128-bit<FancyComplexDouble>` (as defined in
:code:`complex.h` by the C99 standard)
:Logical/Boolean:
:py:func:`8-bit<FancyLogical>`
:Character:
:py:func:`8-bit<FancyCharacter>`
The aliases for these types are listed below, in the :ref:`type classes
<classes>` section. All aliases are imported into the namespace of the
module.
In addition to these, **pointer and array types can be created from basic
types** using the :py:func:`pointer` function or multipliying them by the
desired array length, respectively. C-style :ref:`array declaration <array>`
can be mimicked by indexing type classes, which looks way prettier.
.. autofunction:: pointer
.. _array:
.. admonition:: Array types
:class: note
To create an array class, simply do:
.. code-block:: python
import fancytypes as ft
my_array = 5 * ft.real64
print(my_array) # Prints "fancytypes.real64_array_5"
# C-style array declaration is also supported
my_char_array = ft.character[8]
print(my_char_array) # Prints "fancytypes.character_array_8"
It is advised to **use NumPy arrays whenever posible instead ctypes
arrays**, but the package provides them nonetheless.
The package provides some functions to make interoperability easier:
* :py:func:`strarray` - Create NumPy character arrays from Python strings
* :py:func:`ptrarray` - Create an array of pointers to NumPy arrays on a
list
* :py:func:`nparray` - Explicitly declare procedure arguments as NumPy
arrays
.. autofunction:: strarray
.. autofunction:: ptrarray
.. autofunction:: nparray
*Placeholder text for references to documentation that I will write eventually*
.. _classes:
Type classes
------------
.. autoclass:: FancyInteger()
.. autoclass:: FancyLongInteger()
.. autoclass:: FancyShortInteger()
.. autoclass:: FancyRealSingle()
.. autoclass:: FancyRealDouble()
.. autoclass:: FancyCharacter()
.. autoclass:: FancyLogical()
.. autoclass:: FancyUnsignedInteger()
.. autoclass:: FancyUnsignedLongInteger()
.. autoclass:: FancyUnsignedShortInteger()
.. autoclass:: FancyComplexSingle()
.. autoclass:: FancyComplexDouble()
"""
from ctypes import (c_int, c_longlong, c_short, c_float, c_double, c_char,
c_bool, c_uint, c_ulonglong, c_ushort, POINTER)
from numpy import (int32, int64, int16, float32, float64, uint32, uint64,
uint16, complex64, complex128, char)
from numpy.ctypeslib import ndpointer
from fancytypes._types import FancyMeta, FancyType, ComplexType
from fancytypes.ctypes import single_complex, double_complex
[docs]
class FancyInteger(FancyType):
'''Class for 32-bit integers.
:Aliases:
* *int32*
* *integer*
* *int*
'''
_ctype_ = c_int
_numpy_ = int32
_alias_ = 'int32'
[docs]
class FancyLongInteger(FancyType):
'''Class for 64-bit integers.
:Aliases:
* *int64*
* *long*
'''
_ctype_ = c_longlong
_numpy_ = int64
_alias_ = 'int64'
[docs]
class FancyShortInteger(FancyType):
'''Class for 16-bit integers.
:Aliases:
* *int16*
* *short*
'''
_ctype_ = c_short
_numpy_ = int16
_alias_ = 'int16'
[docs]
class FancyRealSingle(FancyType):
'''Class for 32-bit real numbers, aka floats or singles (like me).
:Aliases:
* *real32*
* *float32*
* *single*
* *sp*
'''
_ctype_ = c_float
_numpy_ = float32
_alias_ = 'real32'
[docs]
class FancyRealDouble(FancyType):
'''Class for 64-bit real numbers, aka floats-64 or doubles.
:Aliases:
* *real64*
* *float64*
* *double*
* *dp*
'''
_ctype_ = c_double
_numpy_ = float64
_alias_ = 'real64'
[docs]
class FancyCharacter(FancyType):
'''Class for 8-bit characters.
:Aliases:
* *character*
* *char*
'''
_ctype_ = c_char
_numpy_ = '|S'
_alias_ = 'character'
[docs]
class FancyLogical(FancyType):
'''Class for 8-bit logical values, aka booleans or bools.
:Aliases:
* *logical*
* *boolean*
* *bool*
'''
_ctype_ = c_bool
_numpy_ = bool
_alias_ = 'logical'
[docs]
class FancyUnsignedInteger(FancyType):
'''Class for 32-bit unsigned integers.
:Aliases:
* *uint32*
* *uint*
'''
_ctype_ = c_uint
_numpy_ = uint32
_alias_ = 'uint32'
[docs]
class FancyUnsignedLongInteger(FancyType):
'''Class for 64-bit unsigned integers.
:Aliases:
* *uint64*
* *ulong*
'''
_ctype_ = c_ulonglong
_numpy_ = uint64
_alias_ = 'uint64'
[docs]
class FancyUnsignedShortInteger(FancyType):
'''Class for 16-bit unsigned integers.
:Aliases:
* *uint16*
* *ushort*
'''
_ctype_ = c_ushort
_numpy_ = uint16
_alias_ = 'uint16'
[docs]
class FancyComplexSingle(ComplexType):
'''Class for 64-bit complex numbers (two single precision real numbers).
:Aliases:
* *complex64*
'''
_ctype_ = single_complex
_numpy_ = complex64
_alias_ = 'complex64'
[docs]
class FancyComplexDouble(ComplexType):
'''Class for 128-bit complex numbers (two double precision real numbers).
:Aliases:
* *complex128*
'''
_ctype_ = double_complex
_numpy_ = complex128
_alias_ = 'complex128'
[docs]
def pointer(typ):
'''Return a pointer class. These **pointer classes are used to declare
pointer arguments in procedure interfaces**. For pointers to actual
:py:mod:`ctypes` variable instances use :py:func:`cpointer` instead.
:param typ: Type to get a pointer class from
:return: Pointer class
.. code-block:: python
import fancytypes as ft
my_pointer = ft.pointer(ft.real64)
print(my_pointer) # Prints "fancytypes.ptr_real64"
# These can be chained
my_pointer_to_a_pointer = ft.pointer(my_pointer)
print(my_pointer_to_a_pointer) # Prints "fancytypes.ptr_ptr_real64"
'''
# Current workaround solution to let struct/union pointers through
if hasattr(typ, '_fields_'):
return POINTER(typ)
if not hasattr(typ, '_ctype_'):
errmsg = 'fancytypes.pointer must receive a fancytypes type'
raise TypeError(errmsg)
return typ._pointer()
[docs]
def strarray(items, *, strlen=None):
'''Return a NumPy character array from a Python string or a list of
strings. For the later, the longest string sets the length of the array
"rows" that store the individual strings, padding the shorter strings.
.. admonition:: Character encoding
:class: note
Unicode outputs are explicitly disabled to ensure 8-bit elements. This
function was originally meant for paths, so it will not fit all
usecases. ASCII outside of the minimal 8-bit is not supported.
:param items: String or list/tuple of strings
:type items: :py:class:`str`, :py:class:`list` or :py:class:`tuple`
:param strlen: Specify a string length, default is the longest required
:type strlen: :py:class:`int`, *optional*
:return: NumPy character array of 8-bit characters
:rtype: :py:class:`numpy.ndarray`
.. code-block:: python
import fancytypes as ft
my_strings = ['data.dat', 'some_text.txt', 'user.cfg']
my_array = ft.strarray(my_strings)
print(my_array) # Prints "[b'data.dat', b'some_text.txt', b'user.cfg']"
'''
if isinstance(items, str):
items = (items,)
if not isinstance(items, (list, tuple)):
errmsg = 'items must be a list or tuple of strings'
raise TypeError(errmsg)
asarray = char.asarray(items, itemsize=strlen, unicode=False)
return asarray
[docs]
def ptrarray(arrays, typ):
'''Return an array of pointers to NumPy arrays on a list. This can be used
to pass an arbitrary number of arrays to a procedure. These arrays do not
have to be contiguous in memory, which forces us to pass them like this.
:param arrays: List/tuple of arrays
:type arrays: :py:class:`list` or :py:class:`tuple`
:param typ: Type to make the pointers to
:return: :py:mod:`ctypes` array instance
.. code-block:: python
import numpy as np
import fancytypes as ft
my_array_1 = np.array([1, 2, 3, 4, 5], dtype=np.int32)
my_array_2 = np.array([6, 7, 8, 9, 10], dtype=np.int32)
my_array_3 = np.array([11, 12, 13, 14, 15], dtype=np.int32)
my_arrays = [my_array_1, my_array_2, my_array_3]
my_pointer_array = ft.ptrarray(my_arrays, ft.int32)
print(my_pointer_array[0][:5]) # Prints [1, 2, 3, 4, 5]
print(my_pointer_array[1][:5]) # Prints [6, 7, 8, 9, 10]
print(my_pointer_array[2][:5]) # Prints [11, 12, 13, 14, 15]
'''
if not isinstance(arrays, (list, tuple)):
errmsg = 'arrays must be a list or tuple of NumPy arrays'
raise TypeError(errmsg)
if not type(typ) is FancyMeta:
this_class = typ.__name__
errmsg = f'dtype must be a fancytypes type, got {this_class} instead'
raise TypeError(errmsg)
array_type = pointer(typ) * len(arrays)
ptr_array = array_type(*(typ.array(array) for array in arrays))
return ptr_array
[docs]
def nparray(typ, *, ndim=None, shape=None, flags=None):
'''Return a :py:class:`~numpy.ctypeslib.ndpointer` object from
:py:mod:`numpy.ctypeslib`. These can be used in procedure interfaces to
explicitly declare NumPy arrays as arguments. This function is a wrapper
around `ndpointer
<https://numpy.org/doc/stable/reference/routines.ctypeslib.html#numpy.ctypeslib.ndpointer>`_,
and its optional keyword arguments are described there.
:param typ: Type of the array
:return: NumPy ndpointer object
:rtype: :py:class:`~numpy.ctypeslib.ndpointer`
.. code-block:: python
import fancytypes as ft
my_ndpointer = ft.nparray(ft.real64)
print(my_ndpointer) # Prints "numpy.ctypeslib.ndpointer_<f8"
'''
if not type(typ) is FancyMeta:
this_class = typ.__name__
errmsg = f'dtype must be a fancytypes type, got {this_class} instead'
raise TypeError(errmsg)
ctype = typ._ctype_
if hasattr(ctype, '_type_') and not isinstance(ctype._type_, str):
errmsg = 'NumPy array arguments can only be of basic types'
raise TypeError(errmsg)
return ndpointer(typ._numpy_, ndim=ndim, shape=shape, flags=flags)