Skip to main content

Fields

Fields are the most basic type of data containers in taichi.js. Fields are n-dimensional arrays, where each element of the array can be either a primitive, a vector, a matrix, or a struct. Fields always live in GPU memory.

Declarations

To declare a field, use the function ti.field( <element type>, <dimensions> ), here are a few examples:

// 1D field of 1000 floats
let scalarField1D = ti.field(ti.f32, 1000);

// 2D field of 1000x1000 floats
let scalarField2D = ti.field(ti.f32, [1000, 1000]);

// 2D field of 1000x1000 3D float vectors
let vectorField = ti.field(ti.types.vector(ti.f32, 3), [1000, 1000]);

// 1D field of 1000 2x2 float matrices
let matrixField = ti.field(ti.types.matrix(ti.f32, 2, 2), 1000);

// 1D field of 1000 particles
let particleType = ti.types.struct({
pos: ti.types.vector(ti.f32, 3),
vel: ti.types.vector(ti.f32, 3),
});
let particles = ti.field(particleType, 1000);

For vector and matrix fields, there is a shorthand syntax for creating them without having to write ti.types.vector/matrix:

// 2D field of 1000x1000 3D float vectors
let vectorField = ti.Vector.field(3, ti.f32, [1000, 1000]);

// 1D field of 1000 2x2 float matrices
let matrixField = ti.Matrix.field(2, 2, ti.i32, 1000);

When the field is first declared, it is 0-intialized, meaning that all the values in the field are initialized to 0.

Notice that fields must be declared in normal Javascript code. In kernel scope, you can read and write from fields in taichi.js kernels, but you cannot create new ones.

Accessing Fields in Kernels

taichi.js kernel code can access fields using the indexing syntax field[index]. Let's see an example of this for 1D fields first. (Reminder: as previously explained in the Kernels doc page, you need to call ti.addToKernelScope({ ... }) on each field you want to use in kernels).

let scalarField = ti.field(ti.f32, 1000);
let vectorField = ti.field.vector(ti.types.vector(ti.f32, 3), 1000);

ti.addToKernelScope({ scalarField, vectorField });

let k = ti.kernel(() => {
scalarField[0] = 42.0;
let f = scalarField[0]; // 42.0

vectorField[0] = [1.0, 2.0, 3.0];
let v = vectorField[0]; // [1.0, 2.0, 3.0]
...
});

For accessing 2D fields, you can pass-in a vector inside the square-brackets to access them:

let scalarField = ti.field(ti.f32, [1000, 1000]);
ti.addToKernelScope({ scalarField });

let k = ti.kernel(() => {
scalarField[[123, 456]] = 42.0;
let f = scalarField[[123, 456]]; // 42.0
...
});

A very common pattern in taichi.js code is to process every element of a field in parallel. This can be done using a parallelized for-loop whose range is the same as the index of the field:

let f1 = ti.field(ti.f32, 1000);
let f2 = ti.field(ti.f32, [1000, 1000]);
ti.addToKernelScope({ f1, f2 });

let increment = ti.kernel(() => {
for(let i of ti.range(1000)){
f1[i] = f1[i] + 1
}
for(let I of ti.ndrange(1000, 1000)){
f2[I] = f2[I] + 1
}
})

Accessing Fields in Javascript Scope

You can also access fields from Javascript-code. Notice that this is a costly operation, as it requires copying data between the GPU and the CPU. Here's the API for reading/writing individual elements of fields:

let scalarField = ti.field(ti.f32, [1000, 1000]);
// writing
await scalarField.set([123, 456], 42.0)
// reading
console.log(await scalarField.get([123, 456])) // 42.0

Beyond the set and get methods for accessing inidividual elements, there is also an API for copying the entire field from GPU to Javascript:

let scalarField = ti.field(ti.i32, 10);
// writing
await scalarField.fromArray([0,1,2,3,4,5,6,7,8,9])
// reading
console.log(await scalarField.toArray()) //[0,1,2,3,4,5,6,7,8,9]

Accessing Metadata

If you have a ti.field named f, you can find its dimensions using f.dimensions. This works in both Javascript-scope and kernel-scope:

let scalarField2D = ti.field(ti.f32, [1000, 1000]);
console.log(scalarField2D.dimensions) // [1000, 1000]

ti.addToKernelScope({ scalarField2D });
let k = ti.kernel(() => {
return scalarField2D.dimensions
})
console.log(await k())

Fields as Template Argument to Kernels

You can pass fields as argument to kernels by declaring a template argument:

let scalarField = ti.field(ti.f32, [1000, 1000]);
let increment = ti.kernel(
{ f: ti.template() },
(f) => {
for(let I of ti.ndrange(f.dimensions[0], f.dimensions[1])){
f[I] = f[I] + 1
}
}
)
await increment(scalarField)

We will have a much more in-depth discussion of the template mechanism of taichi.js in a separate page.