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.dataclassrecommended

  • @qd.data_oriented — for classes that define @qd.kernel methods, 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?

dataclasses.dataclass

yes

yes

yes

yes

yes

no [*1]

@qd.data_oriented

yes

yes

no

yes

yes

yes

@qd.struct, @qd.dataclass

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