Skip to content
Open
Changes from 2 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
32 changes: 13 additions & 19 deletions docs/source/user_guide/compound_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ The following compound types are available:
| Member declaration | type-annotated class fields | live attributes (no annotations) | type-annotated class fields |
| Kernel-arg annotation | `MyStruct` (the dataclass type) | `qd.Template` | `MyStruct` (the struct type) |

Note on the "Members can be tensors" row for `@qd.dataclass`: a `@qd.dataclass`'s members must be primitives, fixed vectors, or fixed matrices — not `qd.field` / `qd.ndarray`. However, *allocating* a `@qd.dataclass` as a tensor of structs in SoA layout (`MyStruct.field(shape=(N,), layout=qd.Layout.SOA)`) extrudes each member into its own length-`N` tensor — so the resulting *collection* effectively behaves like a struct of parallel tensors, even though the `@qd.dataclass` type itself doesn't have tensor-typed members. See the [`@qd.dataclass` section](#qddataclass-qdtypesstruct) below.

> ⚠️ **Deprecation: `@dataclasses.dataclass` instance passed via `qd.Template`.**
> Passing a `@dataclasses.dataclass` instance into a `qd.Template`-annotated kernel parameter is not supported and emits a `DeprecationWarning` at compile time. In a future release it will become an error.

Expand Down Expand Up @@ -171,7 +169,7 @@ sim.step()

### Primitive members

Primitive members on `self` (e.g. `int`, `float`, `bool`, `enum.Enum`) are supported, but they are treated as **template values**: each distinct primitive value across instances triggers a new kernel compilation, with the value baked into the kernel IR.
Primitive members on `self` (e.g. `int`, `float`, `bool`, `enum.Enum`) are supported, but they are treated as **template values**: each distinct primitive value across instances triggers a new kernel compilation, with the value baked into the compiled kernel.

```python
@qd.data_oriented
Expand All @@ -191,7 +189,7 @@ Simulation(200).step() # compiles kernel #2 with n=200 baked in

### Tensor members

`@qd.data_oriented` classes may hold tensor members of any backend: `qd.field`, `qd.ndarray`, or `qd.Tensor`.
`@qd.data_oriented` classes may hold tensor members of any backend: `qd.field`, `qd.ndarray`, or [qd.Tensor](tensor.md).

```python
@qd.data_oriented
Expand Down Expand Up @@ -240,6 +238,8 @@ A `@qd.dataclass` can be turned into a tensor of structs (e.g. `MyStruct.field(s
- **Struct-of-arrays (SoA)** (`qd.Layout.SOA`): extrudes each member of the struct into its own tensor of length `N`.
- **Array-of-structs (AoS)** (`qd.Layout.AOS`): the storage is an array of `N` struct cells laid out contiguously in memory. AoS is only available with `qd.field` backing.

Note that although a `@qd.dataclass`'s members can't themselves be tensors, allocating one in SoA layout (`MyStruct.field(shape=(N,), layout=qd.Layout.SOA)`) extrudes each member into its own length-`N` tensor — so the resulting *collection* effectively behaves like a struct of parallel tensors, even though the `@qd.dataclass` type itself doesn't have tensor-typed members.

```python
@qd.dataclass
class Particle:
Expand Down Expand Up @@ -292,7 +292,7 @@ Unlike the other two compound types, `@qd.dataclass` is a real kernel-side type

## Nesting compatibility

This table summarizes which member types are allowed inside which container type. "yes" means the member is walked correctly when the container is passed to a kernel; "no" means the member is ignored or the combination raises an error.
This table summarizes which member types are allowed inside which container type. "yes" means the member is handled correctly when the container is passed to a kernel; "no" means the member is ignored or the combination raises an error.

| Container ↓     /     Member → | `qd.ndarray` | `qd.field` | primitive | `dataclasses.dataclass` | `@qd.data_oriented` | `@qd.dataclass` |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
Expand All @@ -302,17 +302,11 @@ This table summarizes which member types are allowed inside which container type

### Outer kernel-arg annotation

The outermost annotation you put on the kernel parameter determines how the container is walked:

| Annotation | Kernel-arg walker | Notes |
|---|---|---|
| `qd.types.NDArray[...]` | ndarray slot | leaf-level only |
| `MyDataclass` (dataclass type) | per-member flatten using annotations | recommended for `@dataclasses.dataclass`; needs every member to have a quadrants-typed annotation |
| `qd.Template` | value-driven walk of `vars(self)` | for `@qd.data_oriented` containers (and primitives). **Not** supported for `@dataclasses.dataclass` — see the [deprecation notice in Overview](#overview). |

Practical consequence:
The outermost annotation you put on the kernel parameter should match the parameter type as follows:

- **`@qd.data_oriented` containers** must be passed via `qd.Template` (or be the `self` of a `@qd.kernel` method on a `@qd.data_oriented` class). Using a typed-dataclass annotation on the outermost arg errors.
| Kernel parameter compound type | Annotation |
| `@dataclasses.dataclass` | `MyDataclass` (dataclass type) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the table separator row

The new table goes directly from the header to the first data row, so Markdown/MyST will not recognize it as a table; readers will see the raw pipe-delimited text instead of the intended annotation matrix. Please add a delimiter row such as |---|---| before this row.

Useful? React with 👍 / 👎.

| `@qd.data_oriented` | `qd.Template` |
Comment on lines +307 to +309

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The markdown table is missing the required separator row between the header and data rows. Without the separator row (e.g., |---|---|), the table will not render correctly.

The table should be:

| Kernel parameter compound type | Annotation |
|---|---|
| `@dataclasses.dataclass`       | `MyDataclass` (dataclass type) |
| `@qd.data_oriented`            | `qd.Template` |
Suggested change
| Kernel parameter compound type | Annotation |
| `@dataclasses.dataclass` | `MyDataclass` (dataclass type) |
| `@qd.data_oriented` | `qd.Template` |
| Kernel parameter compound type | Annotation |
|--------------------------------|------------------------------|
| `@dataclasses.dataclass` | `MyDataclass` (dataclass type) |
| `@qd.data_oriented` | `qd.Template` |

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


### Reassigning ndarray members

Expand All @@ -321,7 +315,7 @@ For `@qd.data_oriented` containers passed via `qd.Template`, reassigning an ndar
### Restrictions

- **`@qd.dataclass` cannot contain `qd.ndarray` or `qd.field` members.** See the [`@qd.dataclass`](#qddataclass-qdtypesstruct) section above for the full list of allowed member types. (The function-form factory `qd.types.struct(...)` has the same restrictions.)
- **A typed-dataclass kernel-arg annotation cannot have a `@qd.data_oriented` member type** — errors clearly at compile time. Typed-dataclass kernel args are flattened from annotations, but `@qd.data_oriented` carries no per-member annotations, so its members can only be walked from the live instance, which only happens on the `qd.Template` path.
- **Declare all ndarray members on a `@qd.data_oriented` class in `__init__`.** The template-mapper caches the set of ndarray-attribute paths reachable from each instance on its first kernel launch — *per instance*, not per class — so two instances of the same class can legitimately carry different ndarray attribute sets (e.g. an optional `*_adjoint_cache` member that's only allocated when `requires_grad=True`). But:
- **Deleting an ndarray attribute** that was present on an instance's first launch raises `AttributeError` on the next launch on that instance (the cached path still tries to `getattr` the missing attribute).
- **Adding a new ndarray attribute after first launch** on a given instance won't be tracked by the args-hash invalidator on that instance — reassigning the new attribute to a tensor of a different `dtype` / `ndim` after that point will silently reuse the originally compiled kernel.
- **A typed-dataclass kernel-arg annotation cannot have a `@qd.data_oriented` member type** — errors clearly at compile time
- **Declare all ndarray members on a `@qd.data_oriented` class in `__init__`.**
- **Deleting an ndarray attribute** that was present on an `@qd.data_oriented` instance's first launch raises `AttributeError` on the next launch on that instance.
- **Adding a new ndarray attribute after first launch** on a given `@qd.data_oriented` instance will cause incorrect undefined behavior.
Loading