Compound types#
Overview#
It can be useful to combine multiple ndarrays or fields together into a single struct-like object that can be passed into kernels, and into @qd.func’s.
The following compound types are available:
dataclasses.dataclass— recommended@qd.data_oriented— for classes that define@qd.kernelmethods, cannot contain ndarrays@qd.struct/@qd.dataclass— legacy, field-only
type |
can be passed to qd.kernel? |
can be passed to qd.func? |
can contain ndarray? |
can contain field? |
can be nested? |
supports differentiation? |
|---|---|---|---|---|---|---|
|
yes |
yes |
yes |
yes |
yes |
no [*1] |
|
yes |
yes |
no |
yes |
yes |
yes |
|
yes |
yes |
no |
yes |
yes |
yes |
Recommendation#
Use dataclasses.dataclass for new code. It supports both fields and ndarrays, can be nested, and uses standard Python — no Quadrants-specific decorator needed.
The other compound types exist for historical reasons.
dataclasses.dataclass#
dataclasses.dataclass allows you to create structs containing:
ndarrays
fields
primitive types
These structs:
can be passed into kernels (
@qd.kernel) and sub-functions (@qd.func)can be combined with other parameters in the function signature
do not affect runtime performance compared to passing elements directly as parameters
can be nested (a dataclass can contain other dataclasses)
The members are read-only. However, ndarrays and fields are stored as references (pointers), so the contents of the ndarrays and fields can be freely mutated by the kernels and @qd.funcs.
Example#
import quadrants as qd
from dataclasses import dataclass
qd.init(arch=qd.gpu)
@dataclass
class MyStruct:
a: qd.types.NDArray[qd.i32, 1]
b: qd.types.NDArray[qd.i32, 1]
a = qd.ndarray(qd.i32, shape=(55,))
b = qd.ndarray(qd.i32, shape=(57,))
@qd.kernel
def k1(my_struct: MyStruct) -> None:
my_struct.a[35] += 3
my_struct.b[37] += 5
my_struct = MyStruct(a=a, b=b)
k1(my_struct)
print("my_struct.a[35]", my_struct.a[35])
print("my_struct.b[37]", my_struct.b[37])
Output:
my_struct.a[35] 3
my_struct.b[37] 5
Nesting#
Dataclasses can contain other dataclasses:
@dataclass
class Inner:
x: qd.types.NDArray[qd.f32, 1]
@dataclass
class Outer:
inner: Inner
y: qd.types.NDArray[qd.f32, 1]
@qd.kernel
def k2(s: Outer) -> None:
s.inner.x[0] = 1.0
s.y[0] = 2.0
qd.data_oriented#
@qd.data_oriented is designed for classes that define @qd.kernel methods as class members. It wraps these methods to correctly bind self during kernel compilation.
@qd.data_oriented
class Simulation:
def __init__(self, n):
self.x = qd.field(qd.f32, shape=n)
@qd.kernel
def step(self):
for i in self.x:
self.x[i] += 1.0
sim = Simulation(100)
sim.step()
@qd.data_oriented objects can also be passed as qd.Template parameters to kernels defined outside the class, and they support nesting (one @qd.data_oriented struct containing another).
qd.struct / qd.dataclass#
@qd.struct (and its alias @qd.dataclass) is a Quadrants-native struct type. It can only contain fields and primitive types, not ndarrays.
@qd.dataclass
class Particle:
pos: qd.types.vector(3, qd.f32)
vel: qd.types.vector(3, qd.f32)
mass: qd.f32