generated from ni/github-repo-template
-
Notifications
You must be signed in to change notification settings - Fork 3
complex: Add support for NumPy arrays of complex integers #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6c2373a
nitypes: Add complex integer support
bkeryan 2d89b8b
complex: Add scalar overloads and tests
bkeryan 6f6c5df
complex: Revise docs
bkeryan 7f58acd
tests: Use the type alias
bkeryan 1eaa28d
tests: Add test cases for array shape
bkeryan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| """Complex number data types for NI Python APIs. | ||
|
|
||
| ================ | ||
| Complex Integers | ||
| ================ | ||
|
|
||
| Some NI driver APIs (such as NI-FGEN, NI-SCOPE, NI-RFSA, and NI-RFSG) use complex numbers to | ||
| represent I/Q data. Python and NumPy have native support for complex floating-point numbers, but | ||
| not complex integers, so the :mod:`nitypes.complex` submodule provides a NumPy representation of | ||
| complex integers. | ||
|
|
||
| :any:`ComplexInt32DType` is a NumPy structured data type object representing a complex integer with | ||
| 16-bit ``real`` and ``imag`` fields. This structured data type has the same memory layout as the | ||
| ``NIComplexI16`` C struct used by NI driver APIs. | ||
|
|
||
| For more information about NumPy structured data types, see the | ||
| :ref:`NumPy documentation on structured arrays <numpy:structured_arrays>`. | ||
|
|
||
| .. note:: | ||
| In ``NIComplexI16``, the number 16 refers to the number of bits in each field. In | ||
| :any:`ComplexInt32DType`, the number 32 refers to the total number of bits, following the | ||
| precedent set by NumPy's other complex types. For example, :any:`numpy.complex128` contains | ||
| 64-bit ``real`` and ``imag`` fields. | ||
|
|
||
| Constructing arrays of complex integers | ||
| --------------------------------------- | ||
|
|
||
| You can construct an array of complex integers from a sequence of tuples using :func:`numpy.array`: | ||
|
|
||
| >>> import numpy as np | ||
| >>> np.array([(1, 2), (3, 4)], dtype=ComplexInt32DType) | ||
| array([(1, 2), (3, 4)], dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
|
|
||
| Likewise, you can construct an array of complex integer zeros using :func:`numpy.zeros`: | ||
|
|
||
| >>> np.zeros(3, dtype=ComplexInt32DType) | ||
| array([(0, 0), (0, 0), (0, 0)], dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
|
|
||
| Indexing and slicing | ||
| -------------------- | ||
|
|
||
| Indexing the array gives you a complex integer structured scalar: | ||
|
|
||
| >>> x = np.array([(1, 2), (3, 4), (5, 6)], dtype=ComplexInt32DType) | ||
| >>> x[0] | ||
| np.void((1, 2), dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
| >>> x[1] | ||
| np.void((3, 4), dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
|
|
||
| .. note: | ||
| NumPy displays :any:`numpy.void` because the :any:`ComplexInt32DType` structured data type has | ||
| a base type of :any:`numpy.void`. Using a different base type such as :any:`numpy.int32` | ||
| would have benefits, such as making it easier to convert array elements to/from | ||
| :any:`numpy.int32`, but it would also have drawbacks, such as making it harder to initialize | ||
| the array using a sequence of tuples. | ||
|
|
||
| You can index a complex integer structured scalar to get the real and imaginary parts: | ||
|
|
||
| >>> x[0][0] | ||
| np.int16(1) | ||
| >>> x[0][1] | ||
| np.int16(2) | ||
|
|
||
| You can also index by the field names ``real`` and ``imag``: | ||
|
|
||
| >>> x[0]['real'] | ||
| np.int16(1) | ||
| >>> x[0]['imag'] | ||
| np.int16(2) | ||
|
|
||
| Or you can index the entire array by the field names ``real`` and ``imag``: | ||
|
|
||
| >>> x['real'] | ||
| array([1, 3, 5], dtype=int16) | ||
| >>> x['imag'] | ||
| array([2, 4, 6], dtype=int16) | ||
|
|
||
| Arrays of complex integers support slicing and negative indices like any other array: | ||
|
|
||
| >>> x[0:2] | ||
| array([(1, 2), (3, 4)], dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
| >>> x[1:] | ||
| array([(3, 4), (5, 6)], dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
| >>> x[-1] | ||
| np.void((5, 6), dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
|
|
||
| Conversion | ||
| ---------- | ||
|
|
||
| To convert a complex integer structured scalar to a tuple, use the :any:`numpy.ndarray.item` | ||
| method: | ||
|
|
||
| >>> x[0].item() | ||
| (1, 2) | ||
| >>> [y.item() for y in x] | ||
| [(1, 2), (3, 4), (5, 6)] | ||
|
|
||
| To convert NumPy arrays between between different complex number data types, use the | ||
| :func:`convert_complex` function: | ||
|
|
||
| >>> convert_complex(np.complex128, x) | ||
| array([1.+2.j, 3.+4.j, 5.+6.j]) | ||
| >>> convert_complex(ComplexInt32DType, np.array([1.23+4.56j])) | ||
| array([(1, 4)], dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
|
|
||
| You can also use :func:`convert_complex` with NumPy scalars: | ||
|
|
||
| >>> convert_complex(np.complex128, x[0]) | ||
| np.complex128(1+2j) | ||
| >>> convert_complex(ComplexInt32DType, np.complex128(3+4j)) | ||
| np.void((3, 4), dtype=[('real', '<i2'), ('imag', '<i2')]) | ||
|
|
||
| .. note:: | ||
| As of NumPy 2.2, shape typing is still under development, so its type hints do not reflect that | ||
| many operations coerce zero-dimensional arrays to :any:`numpy.generic`. The type hints for the | ||
| scalar overloads of :func:`convert_complex` follow this precedent and return an | ||
| :any:`numpy.ndarray`. This behavior may change in a future release. | ||
|
|
||
| Mathematical operations | ||
| ----------------------- | ||
|
|
||
| Structured arrays of complex integers do not support mathematical operations. Convert | ||
| them to arrays of complex floating-point numbers before doing any sort of math or analysis. | ||
| """ | ||
|
|
||
| from nitypes.complex._conversion import convert_complex | ||
| from nitypes.complex._dtypes import ComplexInt32Base, ComplexInt32DType | ||
|
|
||
| __all__ = ["convert_complex", "ComplexInt32DType", "ComplexInt32Base"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from typing import Any, TypeVar, cast, overload | ||
|
|
||
| import numpy as np | ||
| import numpy.typing as npt | ||
|
|
||
| from nitypes._arguments import validate_dtype | ||
| from nitypes._exceptions import unsupported_dtype | ||
| from nitypes.complex._dtypes import ComplexInt32DType | ||
|
|
||
| _Item_co = TypeVar("_Item_co", bound=Any) | ||
| _ScalarType = TypeVar("_ScalarType", bound=np.generic) | ||
| _Shape = TypeVar("_Shape", bound=tuple[int, ...]) | ||
|
|
||
| _COMPLEX_DTYPES = ( | ||
| np.complex64, | ||
| np.complex128, | ||
| ComplexInt32DType, | ||
| ) | ||
|
|
||
| _FIELD_DTYPE = { | ||
| np.dtype(np.complex64): np.float32, | ||
| np.dtype(np.complex128): np.float64, | ||
| ComplexInt32DType: np.int16, | ||
| } | ||
|
|
||
|
|
||
| @overload | ||
| def convert_complex( | ||
| requested_dtype: type[_ScalarType] | np.dtype[_ScalarType], | ||
| value: np.ndarray[_Shape, Any], | ||
| ) -> np.ndarray[_Shape, np.dtype[_ScalarType]]: ... | ||
|
|
||
|
|
||
| @overload | ||
| def convert_complex( | ||
| requested_dtype: npt.DTypeLike, value: np.ndarray[_Shape, Any] | ||
| ) -> np.ndarray[_Shape, Any]: ... | ||
bkeryan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| # https://numpy.org/doc/2.2/reference/typing.html#d-arrays | ||
| # "While thus not strictly correct, all operations are that can potentially perform a 0D-array -> | ||
| # scalar cast are currently annotated as exclusively returning an ndarray." | ||
| @overload | ||
| def convert_complex( | ||
| requested_dtype: type[_ScalarType] | np.dtype[_ScalarType], | ||
| value: np.generic[Any], | ||
| ) -> np.ndarray[tuple[()], np.dtype[_ScalarType]]: ... | ||
|
|
||
|
|
||
| @overload | ||
| def convert_complex( | ||
| requested_dtype: npt.DTypeLike, | ||
| value: np.generic[Any], | ||
| ) -> np.ndarray[tuple[()], Any]: ... | ||
|
|
||
|
|
||
| def convert_complex( | ||
| requested_dtype: npt.DTypeLike, value: np.ndarray[_Shape, Any] | np.generic[Any] | ||
| ) -> np.ndarray[_Shape, Any]: | ||
| """Convert a NumPy array or scalar of complex numbers to the specified dtype. | ||
|
|
||
| Args: | ||
| requested_dtype: The NumPy data type to convert to. Supported data types: | ||
| :any:`numpy.complex64`, :any:`numpy.complex128`, :any:`ComplexInt32DType`. | ||
| value: The NumPy array or scalar to convert. | ||
|
|
||
| Returns: | ||
| The value converted to the specified dtype. | ||
| """ | ||
| validate_dtype(requested_dtype, _COMPLEX_DTYPES) | ||
| if requested_dtype == value.dtype: | ||
| return cast(np.ndarray[_Shape, Any], value) | ||
| elif requested_dtype == ComplexInt32DType or value.dtype == ComplexInt32DType: | ||
| # ndarray.view on scalars requires the source and destination types to have the same size, | ||
| # so reshape the scalar into an 1-element array before converting and index it afterwards. | ||
| # shape == () means this is either a scalar (np.generic) or a 0-dimension array, but mypy | ||
| # doesn't know that. | ||
| if value.shape == (): | ||
| return cast( | ||
| np.ndarray[_Shape, Any], | ||
| _convert_complexint32_array(requested_dtype, value.reshape(1))[0], | ||
| ) | ||
| else: | ||
| return _convert_complexint32_array( | ||
| requested_dtype, cast(np.ndarray[_Shape, Any], value) | ||
| ) | ||
| else: | ||
| return value.astype(requested_dtype) | ||
|
|
||
|
|
||
| def _convert_complexint32_array( | ||
| requested_dtype: npt.DTypeLike | type[_ScalarType] | np.dtype[_ScalarType], | ||
| value: np.ndarray[_Shape, Any], | ||
| ) -> np.ndarray[_Shape, np.dtype[_ScalarType]]: | ||
| if not isinstance(requested_dtype, np.dtype): | ||
| requested_dtype = np.dtype(requested_dtype) | ||
|
|
||
| requested_field_dtype = _FIELD_DTYPE.get(requested_dtype) | ||
| if requested_field_dtype is None: | ||
| raise unsupported_dtype("requested data type", requested_dtype, _COMPLEX_DTYPES) | ||
|
|
||
| value_field_dtype = _FIELD_DTYPE.get(value.dtype) | ||
| if value_field_dtype is None: | ||
| raise unsupported_dtype("array data type", value.dtype, _COMPLEX_DTYPES) | ||
|
|
||
| return value.view(value_field_dtype).astype(requested_field_dtype).view(requested_dtype) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import numpy as np | ||
| from typing_extensions import TypeAlias | ||
|
|
||
| ComplexInt32Base: TypeAlias = np.void | ||
| """Type alias for the base type of :any:`ComplexInt32DType`, which is :any:`numpy.void`.""" | ||
|
|
||
| ComplexInt32DType = np.dtype((ComplexInt32Base, [("real", np.int16), ("imag", np.int16)])) | ||
| """NumPy structured data type for a complex integer with 16-bit ``real`` and ``imag`` fields.""" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Unit tests for the nitypes.complex package.""" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.