No description
  • C++ 65.4%
  • Shell 19.6%
  • Python 8.7%
  • CMake 6.3%
Find a file
semantic-release-bot 82a136b62c chore(release): 1.2.0-pre.1 [skip ci]
## [1.2.0-pre.1](https://git.aquila-consortium.org/guilhem_lavaux/generic_array/compare/v1.1.0...v1.2.0-pre.1) (2026-06-27)

### Features

* add doc ([d7c9766](d7c9766622))
* add HDF5 IO interop ([ed608ab](ed608aba50))
* add support for C++ allocator ([4038589](4038589976))
* add TBB interop ([28c7a0f](28c7a0f975))
* add TBB interop ([13c34d5](13c34d565c))
2026-06-27 16:46:43 +00:00
.forgejo/workflows ci: bootstrapping releaser/ci 2026-06-13 18:04:23 +02:00
build_tools fix: add breaking change option 2026-06-14 12:39:21 +02:00
docs doc: add better reference manual generation from clang-doc 2026-06-25 07:19:53 +02:00
examples feat: add TBB interop 2026-06-24 08:28:49 +02:00
include/generic_array build: reformat 2026-06-24 08:28:49 +02:00
python fix: run tests for real 2026-06-14 12:28:14 +02:00
tests feat: add TBB interop 2026-06-24 08:28:49 +02:00
.clang-format initial import 2026-06-13 18:03:05 +02:00
.releaserc.json ci: regenerate semantic-release branches for release/1.1 [skip ci] 2026-06-14 13:12:52 +02:00
CHANGELOG.md chore(release): 1.2.0-pre.1 [skip ci] 2026-06-27 16:46:43 +00:00
CLAUDE.md build: update CLAUDE.md 2026-06-25 07:20:59 +02:00
CMakeLists.txt doc: add better reference manual generation from clang-doc 2026-06-25 07:19:53 +02:00
LICENSE doc: add LICENSE 2026-06-14 14:11:44 +02:00
package-lock.json ci: bootstrapping releaser/ci 2026-06-13 18:04:23 +02:00
package.json ci: bootstrapping releaser/ci 2026-06-13 18:04:23 +02:00
README.md fix: cleanup numpy/dlpack/capsule 2026-06-14 09:13:22 +02:00
VERSION.txt chore(release): 1.2.0-pre.1 [skip ci] 2026-06-27 16:46:43 +00:00

GenericArray

A lightweight, dynamic multi-dimensional array / view for C++20. Pure container: shape, strides, memory ownership and zero-copy views, nothing else. Optional, opt-in interop with xtensor (host math) and Kokkos (device buffers).

#include <generic_array/generic_array.hpp>
using namespace ga;

Building

cmake -B build
cmake --build build
ctest --test-dir build

The core is header-only with zero dependencies. xtensor/Kokkos interop and their examples/tests are auto-detected and built only if found.

1. Creating arrays

Two flavors: static rank (GenericArray<T, N>, rank known at compile time, tightest codegen) and dynamic rank (GenericArray<T>, rank known at runtime). Both share the same API.

// Owning allocation, static rank 2
auto a = GenericArray<double, 2>::from_shape({3, 4});

// Owning allocation, dynamic rank
auto b = GenericArray<int>::from_shape(std::vector<size_type>{2, 3, 5});

2. Element access

a(1, 2) = 3.14;
double v = a(1, 2);

operator() only works on host-accessible memory (it asserts otherwise — device buffers are accessed via the Kokkos interop, not directly).

3. Wrapping external memory (no allocation)

std::vector<float> buf(6);
auto w = GenericArray<float, 2>::wrap(buf.data(), {2, 3});
w(0, 0) = 1.0f;   // writes into buf

wrap never owns the memory — the caller keeps buf alive. For strided external buffers, use wrap_strided(ptr, shape, strides, space, offset). If you need the GenericArray to keep the real owner alive (e.g. a Kokkos::View), use adopt(ptr, shape, strides, space, releaser) — see the Kokkos section.

4. Views: transpose, slice, reshape

Views share the underlying buffer (refcounted) — no copies, and the buffer stays alive as long as any view of it exists.

auto a = GenericArray<int, 2>::from_shape({2, 3});

// Transpose -> shape (3,2), same memory
auto t = a.transpose(std::array<size_type,2>{1, 0});
// or, reverse all axes:
auto t2 = a.transpose();

// Slice -> rank-preserving sub-range (begin, end, step) per axis
auto s = a.slice(std::array<Range,2>{ Range{0, 2, 1}, Range{1, 3, 1} });
s(0, 0) = 42;        // writes through to `a`

// Reshape (requires contiguous data)
auto r = a.reshape(std::array<size_type,2>{1, 6});

Dynamic-rank arrays additionally support squeeze() (drop all size-1 axes) and reshape to a different rank:

auto d = GenericArray<int>::from_shape(std::vector<size_type>{1, 6, 1});
auto sq = d.squeeze();                 // rank 1, shape (6)
auto flat = d.reshape(std::vector<size_type>{6}); // rank 1

5. Static <-> dynamic rank conversion

Both directions are O(1) and share storage (no copy):

auto dyn  = a.to_dynamic();            // GenericArray<int, dynamic_rank>
auto back = dyn.to_static<2>();        // throws if dyn.rank() != 2

// non-throwing variant
if (auto opt = dyn.try_to_static<2>())
    use(*opt);

6. Inspecting an array

a.rank();             // number of dimensions
a.shape();            // span<const size_type>
a.strides();          // span<const ssize_type>, in elements
a.size();             // total element count
a.space();            // MemorySpace::Host / Device / Managed
a.is_contiguous();    // row- or col-major contiguous
a.owns_data();        // false for wrap(), true for from_shape()/adopt()
a.data();             // T* to element (0,0,...,0)

7. xtensor interop (host, optional)

#include <generic_array/interop/xtensor.hpp>

auto a = GenericArray<double, 2>::from_shape({2, 3});

// Zero-copy: x aliases a's memory
auto x = ga::xt_interop::as_xtensor(a);
x(0, 0) = 1.0;            // visible in a(0,0)

// Evaluate an xtensor expression into a new, owned GenericArray
auto y = ga::xt_interop::from_xtensor(x * 2.0 + 1.0);

8. Kokkos interop (host/device, optional)

#include <generic_array/interop/kokkos.hpp>

Kokkos::View<double**, Kokkos::HostSpace> v("v", 4, 5);

// Wrap: GenericArray aliases v's memory; v's refcount is captured and
// kept alive for as long as the GenericArray (or any view of it) lives.
auto a = ga::kokkos_interop::wrap(v);   // GenericArray<double, 2>

// Hand a region back to Kokkos as an unmanaged View for a kernel
auto sub  = a.slice(std::array<Range,2>{Range{1,3,1}, Range{0,5,1}});
auto kview = ga::kokkos_interop::as_kokkos_view<Kokkos::HostSpace>(sub);

If Kokkos was built with CUDA, compile with a CUDA-capable compiler (e.g. Kokkos's nvcc_wrapper):

cmake -B build -DCMAKE_CXX_COMPILER=<kokkos_install>/bin/nvcc_wrapper

9. Python interop (optional)

GenericArray provides Python bindings via nanobind with support for:

  • Python Buffer Protocol for zero-copy NumPy integration
  • DLPack for cross-framework tensor exchange (PyTorch, JAX, etc.)

Building with Python support

GenericArray can automatically fetch nanobind using CMake's FetchContent:

# Enable Python bindings with automatic nanobind download
cmake -B build -DGA_ENABLE_PYTHON=ON \
    -DGA_NANOBIND_FETCH=ON \
    -DCMAKE_BUILD_TYPE=Release

# Build
cmake --build build

# Optionally install the Python module
cmake --install build --component python

Alternatively, if you already have nanobind installed:

# With nanobind from pip
pip install nanobind
cmake -B build -DGA_ENABLE_PYTHON=ON \
    -DGA_NANOBIND_FETCH=OFF \
    -DGA_NANOBIND_ROOT=$(python3 -c "import nanobind; print(nanobind.get_include())")
cmake --build build

# Or with nanobind in a custom location
cmake -B build -DGA_ENABLE_PYTHON=ON \
    -DGA_NANOBIND_FETCH=OFF \
    -DGA_NANOBIND_ROOT=/path/to/nanobind/include
cmake --build build

Note: The first method (FetchContent) is recommended as it automatically downloads and uses a compatible version of nanobind.

Python usage

import generic_array as ga
import numpy as np

# Create a 3x4 float32 array
arr = ga.GenericArray_float32([3, 4])

# Element access
arr[0, 0] = 1.0
print(arr[1, 2])  # 0.0

# NumPy interop (zero-copy via Buffer Protocol)
np_arr = np.asarray(arr)  # Zero-copy for contiguous arrays
np_arr[0, 0] = 2.0
print(arr[0, 0])  # 2.0 (mutual mutation)

# Convert to/from NumPy (both zero-copy; mutations are mutual)
np_view = ga.to_numpy(arr)      # Zero-copy view; arr keeps the buffer alive
arr_view = ga.from_numpy(np_view)  # Zero-copy view; arr_view keeps np_view alive

# DLPack interop
import torch  # if available
dl_tensor = ga.to_dlpack(arr)     # Export to DLPack
arr_back = ga.from_dlpack(dl_tensor)  # Import from DLPack
torch_tensor = torch.from_dlpack(dl_tensor)  # Import to PyTorch

# View operations
arr_t = arr.transpose()          # Shape: [4, 3]
arr_slice = arr.slice([ga.Range(0, 2), ga.Range(1, 3)])
arr_flat = arr.reshape([1, 12])
arr_squeezed = ga.GenericArray_float32([1, 5, 1]).squeeze()  # Shape: [5]

Supported types

  • GenericArray_float32 / float64 / float32_2D / float32_3D / etc.
  • GenericArray_int32 / int64 / int8
  • GenericArray_uint8 / uint32 / uint64
  • GenericArray_bool

Both dynamic-rank and static-rank (1D-4D) variants are available.

Python Buffer Protocol

GenericArray implements the Python Buffer Protocol (PEP 3118), enabling:

  • Zero-copy conversion to NumPy arrays via np.asarray()
  • Mutual mutations between GenericArray and NumPy
  • Compatibility with any Python library that supports the buffer protocol

DLPack Support

DLPack enables zero-copy tensor exchange between frameworks:

  • GenericArray ↔ NumPy
  • GenericArray ↔ PyTorch (if available)
  • GenericArray ↔ JAX (if available)
  • GenericArray ↔ any DLPack-compatible framework

Python API Reference

See the Python API documentation for complete type hints.


What this class is not

No arithmetic, no broadcasting, no iterators, no I/O, no automatic host/device mirroring. It's a metadata + buffer-lifetime layer — build numeric operations on top via the xtensor/Kokkos/Python adapters.