Skip to content
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

Convert ndarray scalars to Span<T> using the buffer protocol #187

Merged
merged 55 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b778aa8
Add numpy as a dependency for integration tests
tonybaloney Sep 5, 2024
73a9e3d
Expose ndarray scalars using the Python Buffer protocol
tonybaloney Sep 5, 2024
082c9b4
Add float scalars and test
tonybaloney Sep 5, 2024
7ffeb1a
Update src/CSnakes.Runtime/Python/IPyBuffer.cs
tonybaloney Sep 5, 2024
5cae82f
Update src/CSnakes.Runtime/Python/PyBuffer.cs
tonybaloney Sep 5, 2024
d339e8d
Update src/CSnakes.Runtime/Python/PyBuffer.cs
tonybaloney Sep 5, 2024
9dc0ddc
Update src/CSnakes.Runtime/Python/PyBuffer.cs
tonybaloney Sep 5, 2024
cb54930
Update src/CSnakes.Runtime/CPython/Buffer.cs
tonybaloney Sep 5, 2024
9e1f0c1
Update src/CSnakes.Runtime/CPython/Buffer.cs
tonybaloney Sep 5, 2024
0693a1b
Marshall format into a string
tonybaloney Sep 5, 2024
7f52a09
Add more conversions
tonybaloney Sep 5, 2024
6130c13
Using single-point precision for the float32 test
tonybaloney Sep 5, 2024
be317a8
Add byte-order and alignment accessor
tonybaloney Sep 5, 2024
9225cb7
Add the common format string types to the class
tonybaloney Sep 5, 2024
f44a778
Fix the struct sequence and add a 2D test
tonybaloney Sep 5, 2024
ff706ef
Add support for 2D vectors using the performance community extensions
tonybaloney Sep 6, 2024
31bab09
Add a test to verify that is writable
tonybaloney Sep 6, 2024
1840a78
Add some length and size checks
tonybaloney Sep 6, 2024
ec1f92f
Platform independent marshalling
tonybaloney Sep 6, 2024
d4077ab
Remove the Python 3.12 checks
tonybaloney Sep 6, 2024
8ee47fa
Add int8 int16 and readonly span
tonybaloney Sep 6, 2024
514454e
Use concrete readonly span implementations
tonybaloney Sep 6, 2024
8e4db0b
Test bytes and bytearray
tonybaloney Sep 6, 2024
deb3fde
Add a gIL to the initial buffer fetch and releases
tonybaloney Sep 6, 2024
e201c90
Explicitly request C Contiguous arrays incase someone tries to give t…
tonybaloney Sep 6, 2024
3f11ee0
Update Buffer.cs
tonybaloney Sep 6, 2024
80d9e7e
Update Buffer.cs
tonybaloney Sep 6, 2024
5d2333e
Update PyBuffer.cs
tonybaloney Sep 6, 2024
5d3d2c2
Update PyBuffer.cs
tonybaloney Sep 6, 2024
3cc7f97
Test ndarray transposition (non-contiguous array) errors, slicings an…
tonybaloney Sep 6, 2024
547db87
Merge branch 'numpy_tests' of https://github.com/tonybaloney/CSnakes …
tonybaloney Sep 6, 2024
75c0abb
Simplify types in API. Add bool, nint and unint conversions
tonybaloney Sep 16, 2024
d93295d
Apply suggestions from code review
tonybaloney Sep 16, 2024
49e06ac
Fix byte order enum parsing
tonybaloney Sep 16, 2024
c4f7948
Platform agnostic implementations
tonybaloney Sep 16, 2024
dabe33a
Add documentation
tonybaloney Sep 16, 2024
aacea48
Merge branch 'main' into numpy_tests
tonybaloney Sep 16, 2024
71d91f0
Make buffer span methods generic and move the convenience methods to …
tonybaloney Sep 17, 2024
3718b78
Merge branch 'numpy_tests' of https://github.com/tonybaloney/CSnakes …
tonybaloney Sep 17, 2024
ecf8100
Simplify names
tonybaloney Sep 17, 2024
f9f4bfa
Rename extension class
tonybaloney Sep 17, 2024
d6369b3
Tightening up the code a little
aaronpowell Sep 17, 2024
ec1d3c9
Update docs/buffers.md
tonybaloney Sep 17, 2024
d889bbb
Make a big red box with the warning about editing values
tonybaloney Sep 17, 2024
2c18f85
Merge branch 'numpy_tests' of https://github.com/tonybaloney/CSnakes …
tonybaloney Sep 17, 2024
aa110f1
Add remark about contiguous arrays
tonybaloney Sep 17, 2024
befea5c
Rebase from main
tonybaloney Sep 18, 2024
144e30c
Reference Tensor<T>
RussKie Sep 18, 2024
6bb3666
Merge pull request #213 from RussKie/numpy_tests
tonybaloney Sep 18, 2024
8b4dc70
Implement tensor spans
tonybaloney Sep 18, 2024
cdea255
Add extension methods
tonybaloney Sep 18, 2024
835ae1d
Add API type
tonybaloney Sep 18, 2024
3b652ea
Merge remote-tracking branch 'origin/main' into numpy_tests
tonybaloney Sep 18, 2024
f9ea39a
try LD_PRELOAD On tests
tonybaloney Sep 18, 2024
8471a7a
Use generic lib name
tonybaloney Sep 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions docs/buffers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Buffer Protocol and NumPy Arrays

CSnakes supports the Python Buffer Protocol for `bytes` and `bytearray` types. The Buffer Protocol is a low-level interface for reading and writing raw bytes from Python objects.
The `IPyBuffer` interface is used to represent Python objects that support the Buffer Protocol. It has methods for accessing the raw data of the buffer in a Read-Only or Read-Write Span.

Since NumPy ndarrays also support the Buffer Protocol, you can use the `IPyBuffer` interface to efficiently read and write data from NumPy arrays.

`typing.Buffer` (`collections.abc.Buffer`) was introduced in Python 3.12, but for older versions you can import `Buffer` from the `typing_extensions` package on PyPi.

For example:

```python
try:
from collections.abc import Buffer
except ImportError:
from typing_extensions import Buffer

import numpy as np

def example_array() -> Buffer:
return np.array([True, False, True, False, False], dtype=np.bool_)
```

In this example, the `example_array` function returns a NumPy array of boolean values. The `Buffer` type hint indicates that the function returns an object that supports the Buffer Protocol.

From C#, CSnakes will return an implementation of `CSnakes.Runtime.Python.IPyBuffer` that wraps the buffer and provides access to the data as a Span.
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

For example:

```csharp
// Where testModule is a Python module that contains the example_array function
var bufferObject = testModule.ExampleArray();

// check if the buffer is scalar (single dimensional)
if (bufferObject.IsScalar) {

// Get the buffer contents as a Span of bool
Span<bool> result = bufferObject.AsBoolSpan();
Console.WriteLine(result[0]); // True
Console.WriteLine(result[4]); // False
}
```

Since Span is writeable, you can also modify the buffer contents from C# and the changes will be reflected in the Python object.
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

If you want a read-only view of the buffer, you can use the `As[T]ReadOnly` method to get a read-only Span.

```csharp
Span<bool> result = bufferObject.AsBoolReadOnlySpan();
```

## Two-Dimensional Buffers
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved

The `IPyBuffer` interface also provides methods for working with two-dimensional buffers. You can use the `As[T]Span2D` and `As[T]ReadOnlySpan2D` methods to get a two-dimensional Span of the buffer contents.

You can use the `Dimensions` property to get the dimensions of the buffer. The `As[T]Span2D` method will throw an exception if the buffer is not two-dimensional.

For example:

```python
def example_array_2d() -> Buffer:
return np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
```

From .NET you can access the buffer as a two-dimensional Span:

```csharp
// Where testModule is a Python module that contains the example_array_2d function
var result = testModule.ExampleArray2D();

// Get the buffer contents as a two-dimensional Span of int
Span2D<int> result2D = result.AsIntSpan2D();
Console.WriteLine(result2D[0, 0]); // 1

```

var bufferObject = testModule.ExampleArray2D();
tonybaloney marked this conversation as resolved.
Show resolved Hide resolved


## NumPy Type Conversion

The Numpy dtypes are mapped to C# types as follows:

| NumPy dtype | C# Type | Span Method | ReadOnly Span Method | Span2D Method | ReadOnly Span2D Method |
|-------------|---------|-------------|----------------------|---------------|------------------------|
| `bool` | `bool` | `AsBoolSpan`| `AsBoolReadOnlySpan` | `AsBoolSpan2D`| `AsBoolReadOnlySpan2D` |
| `int8` | `sbyte` | `AsSByteSpan`| `AsSByteReadOnlySpan` | `AsSByteSpan2D`| `AsSByteReadOnlySpan2D` |
| `int16` | `short` | `AsShortSpan`| `AsShortReadOnlySpan` | `AsShortSpan2D`| `AsShortReadOnlySpan2D` |
| `int32` | `int` | `AsIntSpan` | `AsIntReadOnlySpan` | `AsIntSpan2D` | `AsIntReadOnlySpan2D` |
| `int64` | `long` | `AsLongSpan` | `AsLongReadOnlySpan` | `AsLongSpan2D` | `AsLongReadOnlySpan2D` |
| `uint8` | `byte` | `AsByteSpan` | `AsByteReadOnlySpan` | `AsByteSpan2D` | `AsByteReadOnlySpan2D` |
| `uint16` | `ushort`| `AsUShortSpan`| `AsUShortReadOnlySpan`| `AsUShortSpan2D`| `AsUShortReadOnlySpan2D`|
| `uint32` | `uint` | `AsUIntSpan` | `AsUIntReadOnlySpan` | `AsUIntSpan2D` | `AsUIntReadOnlySpan2D` |
| `uint64` | `ulong` | `AsULongSpan`| `AsULongReadOnlySpan` | `AsULongSpan2D`| `AsULongReadOnlySpan2D` |
| `float32` | `float` | `AsFloatSpan`| `AsFloatReadOnlySpan` | `AsFloatSpan2D`| `AsFloatReadOnlySpan2D` |
| `float64` | `double`| `AsDoubleSpan`| `AsDoubleReadOnlySpan`| `AsDoubleSpan2D`| `AsDoubleReadOnlySpan2D`|

The `GetItemType()` method can be used to get the C# type of the buffer contents.

## Bytes objects as buffers

In addition to NumPy arrays, you can also use `bytes` and `bytearray` objects as buffers. The `Buffer` type hint can be used to indicate that a function returns a `bytes` or `bytearray` object that supports the Buffer Protocol.

The Buffer Protocol is an efficient way to read and write bytes between C# and Python. Use `AsByteSpan` and `AsByteReadOnlySpan` to access the raw bytes of the buffer.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Check out the [getting started](getting-started.md) guide or check out the [demo
- Uses Python type hinting to generate function signatures with .NET native types
- Supports nested sequence and mapping types (`tuple`, `dict`, `list`)
- Supports default values
- Efficiently access numpy arrays as 1 or 2 dimensional Spans [1](buffers.md)

## Benefits

Expand Down
1 change: 1 addition & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ CSnakes supports the following typed scenarios:
| `typing.Tuple[T1, T2, ...]` | `(T1, T2, ...)` |
| `typing.Optional[T]` | `T?` |
| `typing.Generator[TYield, TSend, TReturn]` | `IGeneratorIterator<TYield, TSend, TReturn>` [1](#generators) |
| `typing.Buffer` | `IPyBuffer` [2](buffers.md) |
| `None` (Return) | `void` |


Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ nav:
- Home: index.md
- Getting Started: getting-started.md
- Reference: reference.md
- Buffer Protocol and NumPy Arrays: buffers.md
- Advanced Usage: advanced.md
- Limitations: limitations.md
- FAQ: faq.md
Expand Down
8 changes: 4 additions & 4 deletions src/CSnakes.Runtime/CPython/Buffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace CSnakes.Runtime.CPython;
internal unsafe partial class CPythonAPI
{
[Flags]
private enum PyBUF
private enum PyBUF : int
{
Simple = 0,
Writable = 0x1,
Expand All @@ -20,12 +20,12 @@ private enum PyBUF
[StructLayout(LayoutKind.Sequential)]
internal struct Py_buffer
{
public IntPtr buf;
public IntPtr obj;
public nint buf;
public nint obj;
public nint len;
public nint itemsize;
public int @readonly;
public Int32 ndim;
public int ndim;
public byte* format;
public nint* shape;
public nint* strides;
Expand Down
63 changes: 21 additions & 42 deletions src/CSnakes.Runtime/Python/IPyBuffer.cs
aaronpowell marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,31 @@
namespace CSnakes.Runtime.Python;
public interface IPyBuffer
{
/// <summary>
/// The bit length of the buffer.
/// </summary>
long Length { get; }

/// <summary>
/// The number of dimensions in the buffer. For a scalar, this is 1.
/// </summary>
int Dimensions { get; }

bool Scalar { get; }
/// <summary>
/// Indicates if the buffer is a scalar (single dimension array).
/// </summary>
bool IsScalar { get; }

Span<byte> AsByteSpan();
Span<sbyte> AsSByteSpan();
Span<short> AsInt16Span();
Span<ushort> AsUInt16Span();
Span<int> AsInt32Span();
Span<long> AsInt64Span();
Span<uint> AsUInt32Span();
Span<ulong> AsUInt64Span();
Span<float> AsFloatSpan();
Span<double> AsDoubleSpan();
/// <summary>
/// Gets the item type of the values in the buffer.
/// </summary>
/// <returns></returns>
Type GetItemType();

ReadOnlySpan<byte> AsByteReadOnlySpan();
ReadOnlySpan<sbyte> AsSByteReadOnlySpan();
ReadOnlySpan<short> AsInt16ReadOnlySpan();
ReadOnlySpan<ushort> AsUInt16ReadOnlySpan();
ReadOnlySpan<int> AsInt32ReadOnlySpan();
ReadOnlySpan<long> AsInt64ReadOnlySpan();
ReadOnlySpan<uint> AsUInt32ReadOnlySpan();
ReadOnlySpan<ulong> AsUInt64ReadOnlySpan();
ReadOnlySpan<float> AsFloatReadOnlySpan();
ReadOnlySpan<double> AsDoubleReadOnlySpan();
Span<T> AsSpan<T>() where T : unmanaged;
ReadOnlySpan<T> AsReadOnlySpan<T>() where T : unmanaged;

Span2D<byte> AsByteSpan2D();
Span2D<sbyte> AsSByteSpan2D();
Span2D<short> AsInt16Span2D();
Span2D<ushort> AsUInt16Span2D();
Span2D<int> AsInt32Span2D();
Span2D<uint> AsUInt32Span2D();
Span2D<long> AsInt64Span2D();
Span2D<ulong> AsUInt64Span2D();
Span2D<float> AsFloatSpan2D();
Span2D<double> AsDoubleSpan2D();
Span2D<T> AsSpan2D<T>() where T : unmanaged;
ReadOnlySpan2D<T> AsReadOnlySpan2D<T>() where T : unmanaged;

ReadOnlySpan2D<byte> AsByteReadOnlySpan2D();
ReadOnlySpan2D<sbyte> AsSByteReadOnlySpan2D();
ReadOnlySpan2D<short> AsInt16ReadOnlySpan2D();
ReadOnlySpan2D<ushort> AsUInt16ReadOnlySpan2D();
ReadOnlySpan2D<int> AsInt32ReadOnlySpan2D();
ReadOnlySpan2D<uint> AsUInt32ReadOnlySpan2D();
ReadOnlySpan2D<long> AsInt64ReadOnlySpan2D();
ReadOnlySpan2D<ulong> AsUInt64ReadOnlySpan2D();
ReadOnlySpan2D<float> AsFloatReadOnlySpan2D();
ReadOnlySpan2D<double> AsDoubleReadOnlySpan2D();
}
}
Loading
Loading