Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Deploy docs to GitHub Pages

on:
push:
branches: ["devel", "main"] # TODO: Set to main only after release
branches: ["devel"]
workflow_dispatch:

permissions:
Expand Down
58 changes: 58 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Changelog

All notable changes to the `cunumpy` library are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.2] - 2026-05-27

### Added
- **Backend Management Helpers**:
- `xp.to_numpy(arr)`: Explicitly move an array to CPU (NumPy).
- `xp.to_cupy(arr)`: Explicitly move an array to GPU (CuPy).
- `xp.to_cunumpy(arr)`: Normalize an array to the currently active backend.
- `xp.get_backend(arr)`: Identify if an array is `"numpy"` or `"cupy"`.
- `xp.is_gpu(arr)` & `xp.is_cpu(arr)`: Quick boolean checks for array residence.
- **Global Backend Control**:
- `xp.set_backend(name)`: Globally switch the active backend at runtime.
- `xp.use_backend(name)`: Context manager for temporary, scoped backend switching.
- `xp.numpy_backend` & `xp.cupy_backend`: Boolean properties to check the globally active backend.
- **Synchronization**:
- `xp.synchronize()`: Blocks until GPU operations are complete (no-op on CPU). Essential for accurate benchmarking.
- **Developer Experience**:
- Added `isort` configuration to `pyproject.toml` with `black` profile compatibility.

### Changed
- **Dynamic Dispatch Architecture**: Refactored `src/cunumpy/xp.py` to use module-level `__getattr__`. This ensures that `cunumpy.<op>` calls always resolve to the currently active backend module, enabling seamless runtime switching via `set_backend`.
- **Type Safety**: Updated `src/cunumpy/__init__.pyi` stubs to provide full IDE autocompletion and type-checking for all new API methods.
- **Documentation**:
- Simplified `README.md` and documentation to exclusively focus on PyPI installation (`pip install cunumpy`).
- Enhanced `quickstart.md` and `api.md` with usage examples for the new backend control and synchronization features.
- **CI/CD**: Restricted GitHub Pages documentation deployment to the `devel` branch only.

### Fixed
- Improved `ArrayBackend` initialization to fallback gracefully to NumPy if CuPy is requested but not installed.
- Fixed symbol accessibility tests to correctly handle custom helper methods in the `cunumpy` namespace.

## [0.1.1] - 2026-05-27

### Added
- **Type Inspection Support**: Added `.pyi` stub files to enable proper symbol inspection and autocompletion for Pylance/VS Code.
- **CI/CD**: Added GitHub Action for automated publishing to PyPI.

### Changed
- Improved import structure in `src/cunumpy/__init__.py` to allow direct access to `xp`.

## [0.1.0] - 2026-05-27

### Added
- **Initial Core Functionality**:
- `ArrayBackend` class for dispatching between NumPy and CuPy.
- Environment variable support (`ARRAY_BACKEND`) for selecting the backend.
- Basic module structure and `src/cunumpy/xp.py`.
- **Project Scaffold**:
- Documentation setup with Sphinx and nbsphinx.
- Initial unit tests and testing infrastructure.
- Basic tutorial notebook and README instructions.
- **Metadata**: Initial configuration of `pyproject.toml` and project metadata.
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,8 @@ Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cun

# Install

Create and activate python environment

```
python -m venv env
source env/bin/activate
pip install --upgrade pip
```

Install the code and requirements with pip

```
pip install -e .
```bash
pip install cunumpy
```

Example usage:
Expand All @@ -30,6 +20,28 @@ arr = xp.array([1,2])

print(type(arr))
print(xp.__version__)

# Convert to NumPy
arr_np = xp.to_numpy(arr)

# Convert to active backend
arr_xp = xp.to_cunumpy(arr)

# Inspect backend
print(xp.get_backend(arr))
print(xp.is_gpu(arr))
print(xp.is_cpu(arr))

# Temporarily switch backend
with xp.use_backend("numpy"):
# This code runs on CPU even if ARRAY_BACKEND=cupy
arr_cpu = xp.zeros(100)

# Set backend globally
xp.set_backend("cupy")

# Synchronize GPU operations (no-op on CPU)
xp.synchronize()
```

# Build docs
Expand Down
48 changes: 45 additions & 3 deletions docs/source/api.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,48 @@
# API

Description of the API
The `cunumpy` package exposes the following helper functions in addition to the standard NumPy/CuPy API.

```{toctree}
:maxdepth: 1
## Backend Management

### `to_numpy(array)`
Converts an array to a NumPy array on the CPU.

### `to_cupy(array)`
Converts an array to a CuPy array on the GPU. Raises `ImportError` if CuPy is not available.

### `to_cunumpy(array)`
Converts an array to the currently active backend.

### `get_backend(array)`
Returns the name of the backend (`"numpy"` or `"cupy"`) for the given array.

### `is_gpu(array)`
Returns `True` if the array is stored on a GPU (CuPy).

### `is_cpu(array)`
Returns `True` if the array is stored on a CPU (NumPy).

## Global Configuration

### `numpy_backend`
Boolean property that returns `True` if the currently active global backend is NumPy.

### `cupy_backend`
Boolean property that returns `True` if the currently active global backend is CuPy.

### `set_backend(backend_name)`
Globally sets the active backend for all `cunumpy` operations. `backend_name` should be `"numpy"` or `"cupy"`.

### `use_backend(backend_name)`
A context manager that temporarily sets the active backend.

```python
with xp.use_backend("numpy"):
# Operations here use NumPy
pass
```

## Hardware Control

### `synchronize()`
Blocks until all preceding GPU operations are complete. This is a no-op when using the NumPy backend.
29 changes: 23 additions & 6 deletions docs/source/index.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
# Welcome
# CuNumpy

Write the documentation of your python package here
Simple wrapper for NumPy and CuPy.

## Quick Start

```python
import cunumpy as xp

# Use it exactly like NumPy
arr = xp.array([1, 2, 3])
print(type(arr))
```

```{toctree}
:maxdepth: 2
:caption: Contents
:caption: Contents:

quickstart
tutorials
api
```

## Installation

quickstart.md
tutorials.md
api.md
```bash
pip install cunumpy
```
68 changes: 45 additions & 23 deletions docs/source/quickstart.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,68 @@
# Quickstart

Clone the repo
## Installation

```
git clone ...
Install `cunumpy` directly from PyPI:

```bash
pip install cunumpy
```

# Install
## Basic Usage

Create and activate python environment
Replace `import numpy as np` with `import cunumpy as xp`. By default, it will use NumPy if CuPy is not installed or configured.

```
python -m venv env
source env/bin/activate
pip install --upgrade pip
```
```python
import cunumpy as xp

Install the code and requirements with pip
# Create an array (automatically uses the active backend)
arr = xp.array([1, 2, 3])

```
pip install -e .
print(f"Array type: {type(arr)}")
print(f"Active backend: {xp.get_backend(arr)}")
```

Example usage:
## Backend Control

```
You can control the backend using environment variables:

```bash
export ARRAY_BACKEND=cupy
```

Or programmatically:

```python
import cunumpy as xp
arr = xp.array([1,2])

print(type(arr))
print(xp.__version__)
# Globally set backend
xp.set_backend("cupy")

# Scoped backend switching
with xp.use_backend("numpy"):
arr_cpu = xp.zeros(10)
print(xp.is_cpu(arr_cpu)) # True

# Explicit conversion
arr_np = xp.to_numpy(arr)
arr_cp = xp.to_cupy(arr)
```

# Build docs
## Synchronization

When using the GPU, it's important to synchronize for accurate timing:

```
make html
cd ../
open docs/_build/html/index.html
```python
import time
import cunumpy as xp

xp.set_backend("cupy")
a = xp.random.rand(1000, 1000)

start = time.time()
b = xp.dot(a, a)
xp.synchronize() # Wait for GPU
end = time.time()

print(f"Elapsed: {end - start:.4f}s")
```
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires = [ "setuptools", "wheel" ]

[project]
name = "cunumpy"
version = "0.1.1"
version = "0.1.2"
description = "Simple wrapper for numpy and cupy. Replace `import numpy as np` with `import cunumpy as xp`."
readme = "README.md"
keywords = [ "python" ]
Expand Down Expand Up @@ -51,3 +51,6 @@ where = [ "src" ]

[tool.setuptools.package-data]
cunumpy = [ "py.typed", "*.pyi" ]

[tool.isort]
profile = "black"
30 changes: 29 additions & 1 deletion src/cunumpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
# cunumpy/__init__.py
from . import xp
from .xp import (
get_backend,
is_cpu,
is_gpu,
set_backend,
synchronize,
to_cunumpy,
to_cupy,
to_numpy,
use_backend,
)

__all__ = ["xp"]
__all__ = [
"xp",
"to_numpy",
"to_cupy",
"to_cunumpy",
"get_backend",
"is_gpu",
"is_cpu",
"use_backend",
"set_backend",
"synchronize",
"numpy_backend",
"cupy_backend",
]


def __getattr__(name: str):
"""Set cunumpy.<name> to cunumpy.xp.<name> (NumPy/CuPy)."""
if name == "numpy_backend":
return xp.numpy_backend
if name == "cupy_backend":
return xp.cupy_backend
return getattr(xp.xp, name)
19 changes: 18 additions & 1 deletion src/cunumpy/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
# Stub file for Pylance/mypy: exposes all numpy symbols so that
# `import cunumpy as xp` followed by `xp.<Tab>` shows numpy completions.
# At runtime the real __init__.py dispatches to numpy or cupy via __getattr__.
from contextlib import contextmanager
from typing import Any, Generator

import numpy as np
from numpy import *
from numpy import __config__, __version__

from . import xp

def to_numpy(array: Any) -> np.ndarray: ...
def to_cupy(array: Any) -> Any: ...
def to_cunumpy(array: Any) -> Any: ...
def get_backend(array: Any) -> str: ...
def is_gpu(array: Any) -> bool: ...
def is_cpu(array: Any) -> bool: ...
@contextmanager
def use_backend(backend: str) -> Generator[None, None, None]: ...
def set_backend(backend: str) -> None: ...
def synchronize() -> None: ...

numpy_backend: bool
cupy_backend: bool
Loading
Loading