Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
43 changes: 43 additions & 0 deletions docs/source/user_guide/flexible_tensors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
% Note for contributors: this page grows incrementally. Each PR in the
% flexible-tensors series (`hp/tensor-stork-N`) adds the section that
% documents whatever new user-visible behaviour landed in that PR.
% Sections describe only currently-shipped functionality.

# Flexible tensors

Quadrants offers two underlying tensor implementations, [`qd.field`](#fields)
and [`qd.ndarray`](#ndarrays). They have different runtime/compile-time
trade-offs, and different physical memory layouts can suit different kernels.

The flexible-tensors API lets you pick both the **backend** and (in a future
release) the **physical layout** on a per-tensor basis at allocation time.
The rest of the system (kernels, fastcache, autograd) stays out of the way.

This page documents the user-facing API as it lands. See
[`tensor_types`](tensor_types.md), [`scalar_tensors`](scalar_tensors.md),
and [`matrix_vector`](matrix_vector.md) for the underlying tensor primitives.

## Choosing a backend: `qd.Backend`

`qd.Backend` is an `IntEnum` with two members:

| Member | Underlying type | When to prefer |
|---|---|---|
| `qd.Backend.FIELD` | `qd.field` | Faster at runtime; recompiles when any dimension size changes. Best for tensors whose shape is effectively static across a run. |
| `qd.Backend.NDARRAY` | `qd.ndarray` | Slightly slower at runtime but avoids recompilation when sizes change. Best for tensors whose shape varies frequently (dynamic batch sizes, growing buffers). |

```python
import quadrants as qd

qd.Backend.FIELD # IntEnum member, value 0
qd.Backend.NDARRAY # IntEnum member, value 1

int(qd.Backend.FIELD) # 0
qd.Backend["FIELD"] # qd.Backend.FIELD
qd.Backend(1) # qd.Backend.NDARRAY
```

The choice is per tensor: a single program can freely mix backends.

Subsequent releases will use this enum to drive the `qd.tensor(...)` factory
and per-tensor layout selection.
1 change: 1 addition & 0 deletions docs/source/user_guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ supported_systems
tensor_types
scalar_tensors
matrix_vector
flexible_tensors
compound_types
static
sub_functions
Expand Down
2 changes: 2 additions & 0 deletions python/quadrants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
tools,
types,
)
from quadrants._flexible import *
from quadrants._funcs import *
from quadrants._lib.utils import warn_restricted_version
from quadrants._logging import *
Expand All @@ -51,6 +52,7 @@ def __getattr__(attr):
del warn_restricted_version

__all__ = [
"Backend",
"ad",
"algorithms",
"experimental",
Expand Down
33 changes: 33 additions & 0 deletions python/quadrants/_flexible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Flexible tensors: per-tensor backend and (later) layout.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Flexible tensors" => "Tensor"


This module is the user-facing entry point for selecting a tensor backend
(``qd.field`` vs ``qd.ndarray``) on a per-tensor basis. Currently it exports
only the :class:`Backend` enum; the ``qd.tensor(...)`` factory and
``layout=`` support land in subsequent PRs.

See ``docs/source/user_guide/flexible_tensors.md`` for the user guide.
"""

from enum import IntEnum

__all__ = ["Backend"]


class Backend(IntEnum):
"""Tensor storage backend.

Each value selects one of Quadrants' two underlying tensor implementations:

- :attr:`FIELD` (``qd.field``): faster at runtime; recompiles kernels
whenever any dimension size changes. Best for tensors whose shape is
effectively static across a run.
- :attr:`NDARRAY` (``qd.ndarray``): slightly slower at runtime but avoids
kernel recompilation when sizes change. Best for tensors whose shape
varies frequently (e.g. dynamic batch sizes, growing buffers).

The choice is made per tensor at allocation time. A single program can
freely mix both backends.
"""

FIELD = 0
NDARRAY = 1
58 changes: 58 additions & 0 deletions tests/python/test_flexible_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Tests for ``qd.Backend`` (PR 1 of the flexible-tensors plan).

Scope: this PR only ships the enum. No factory, no layout, no kernel
integration. Tests cover symbol export, value, name, ordering, and IntEnum
semantics so that downstream PRs can rely on stable behaviour.
"""

from enum import IntEnum

import pytest

import quadrants as qd


def test_backend_is_exported():
assert hasattr(qd, "Backend")


def test_backend_is_intenum():
assert issubclass(qd.Backend, IntEnum)


def test_backend_values():
assert int(qd.Backend.FIELD) == 0
assert int(qd.Backend.NDARRAY) == 1


def test_backend_names():
assert qd.Backend.FIELD.name == "FIELD"
assert qd.Backend.NDARRAY.name == "NDARRAY"


def test_backend_lookup_by_name():
assert qd.Backend["FIELD"] is qd.Backend.FIELD
assert qd.Backend["NDARRAY"] is qd.Backend.NDARRAY


def test_backend_lookup_by_value():
assert qd.Backend(0) is qd.Backend.FIELD
assert qd.Backend(1) is qd.Backend.NDARRAY


def test_backend_int_compare():
assert qd.Backend.FIELD == 0
assert qd.Backend.NDARRAY == 1
assert qd.Backend.FIELD < qd.Backend.NDARRAY


def test_backend_members_are_distinct():
members = list(qd.Backend)
assert len(members) == 2
assert qd.Backend.FIELD in members
assert qd.Backend.NDARRAY in members


def test_backend_invalid_value_raises():
with pytest.raises(ValueError):
qd.Backend(2)
Loading