Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lang] Add parameter 'order' to specify layout for scalar, vector, matrix fields #5617

Merged
merged 5 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
85 changes: 62 additions & 23 deletions python/taichi/lang/impl.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numbers
from types import FunctionType, MethodType
from typing import Iterable
from typing import Iterable, Sequence

import numpy as np
from taichi._lib import core as _ti_core
Expand All @@ -9,7 +9,8 @@
from taichi.lang._ndrange import GroupedNDRange, _Ndrange
from taichi.lang.any_array import AnyArray, AnyArrayAccess
from taichi.lang.enums import Layout
from taichi.lang.exception import TaichiRuntimeError, TaichiTypeError
from taichi.lang.exception import (TaichiRuntimeError, TaichiSyntaxError,
TaichiTypeError)
from taichi.lang.expr import Expr, make_expr_group
from taichi.lang.field import Field, ScalarField
from taichi.lang.kernel_arguments import SparseMatrixProxy
Expand Down Expand Up @@ -526,6 +527,19 @@ def __repr__(self):
"""


def _create_snode(axis_seq: Sequence[int], shape_seq: Sequence[numbers.Number],
same_level: bool):
dim = len(axis_seq)
assert dim == len(shape_seq)
snode = root
if same_level:
snode = snode.dense(axes(*axis_seq), shape_seq)
else:
for i in range(dim):
snode = snode.dense(axes(axis_seq[i]), (shape_seq[i], ))
return snode


@python_scope
def create_field_member(dtype, name, needs_grad, needs_dual):
dtype = cook_dtype(dtype)
Expand Down Expand Up @@ -576,6 +590,7 @@ def create_field_member(dtype, name, needs_grad, needs_dual):
@python_scope
def field(dtype,
shape=None,
order=None,
name="",
offset=None,
needs_grad=False,
Expand All @@ -592,6 +607,7 @@ def field(dtype,
Args:
dtype (DataType): data type of the field.
shape (Union[int, tuple[int]], optional): shape of the field.
order (str, optional): order of the shape laid out in memory.
name (str, optional): name of the field.
offset (Union[int, tuple[int]], optional): offset of the field domain.
needs_grad (bool, optional): whether this field participates in autodiff (reverse mode)
Expand All @@ -604,41 +620,64 @@ def field(dtype,
The code below shows how a Taichi field can be declared and defined::

>>> x1 = ti.field(ti.f32, shape=(16, 8))
>>>
>>> # Equivalently
>>> x2 = ti.field(ti.f32)
>>> ti.root.dense(ti.ij, shape=(16, 8)).place(x2)
"""

if isinstance(shape, numbers.Number):
shape = (shape, )

if isinstance(offset, numbers.Number):
offset = (offset, )

if shape is not None and offset is not None:
assert len(shape) == len(
offset
), f'The dimensionality of shape and offset must be the same ({len(shape)} != {len(offset)})'

assert (offset is None or shape
is not None), 'The shape cannot be None when offset is being set'
>>>
>>> x3 = ti.field(ti.f32, shape=(16, 8), order='ji')
>>> # Equivalently
>>> x4 = ti.field(ti.f32)
>>> ti.root.dense(ti.j, shape=8).dense(ti.i, shape=16).place(x4)

"""
x, x_grad, x_dual = create_field_member(dtype, name, needs_grad,
needs_dual)
x, x_grad, x_dual = ScalarField(x), ScalarField(x_grad), ScalarField(
x_dual)

x._set_grad(x_grad)
x._set_dual(x_dual)

if shape is not None:
if shape is None:
if offset is not None:
raise TaichiSyntaxError('shape cannot be None when offset is set')
if order is not None:
raise TaichiSyntaxError('shape cannot be None when order is set')
else:
if isinstance(shape, numbers.Number):
shape = (shape, )
if isinstance(offset, numbers.Number):
offset = (offset, )
dim = len(shape)
root.dense(index_nd(dim), shape).place(x, offset=offset)
if offset is not None and dim != len(offset):
raise TaichiSyntaxError(
f'The dimensionality of shape and offset must be the same ({dim} != {len(offset)})'
)
axis_seq = []
shape_seq = []
if order is not None:
if dim != len(order):
raise TaichiSyntaxError(
f'The dimensionality of shape and order must be the same ({dim} != {len(order)})'
)
if dim != len(set(order)):
raise TaichiSyntaxError('The axes in order must be different')
for ch in order:
axis = ord(ch) - ord('i')
if axis < 0 or axis >= dim:
raise TaichiSyntaxError(f'Invalid axis {ch}')
axis_seq.append(axis)
shape_seq.append(shape[axis])
else:
axis_seq = list(range(dim))
shape_seq = list(shape)
same_level = order is None
_create_snode(axis_seq, shape_seq, same_level).place(x, offset=offset)
if needs_grad:
root.dense(index_nd(dim), shape).place(x_grad)
_create_snode(axis_seq, shape_seq, same_level).place(x_grad,
offset=offset)
if needs_dual:
root.dense(index_nd(dim), shape).place(x_dual)
_create_snode(axis_seq, shape_seq, same_level).place(x_dual,
offset=offset)
return x


Expand Down
73 changes: 50 additions & 23 deletions python/taichi/lang/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,7 @@ def field(cls,
m,
dtype,
shape=None,
order=None,
name="",
offset=None,
needs_grad=False,
Expand All @@ -1081,6 +1082,7 @@ def field(cls,
m (int): The desired number of columns of the Matrix.
dtype (DataType, optional): The desired data type of the Matrix.
shape (Union[int, tuple of int], optional): The desired shape of the Matrix.
order (str, optional): order of the shape laid out in memory.
name (string, optional): The custom name of the field.
offset (Union[int, tuple of int], optional): The coordinate offset
of all elements in a field.
Expand Down Expand Up @@ -1136,43 +1138,68 @@ def field(cls,
impl.get_runtime().matrix_fields.append(entries)

if shape is None:
assert offset is None, "shape cannot be None when offset is being set"

if shape is not None:
if offset is not None:
raise TaichiSyntaxError(
'shape cannot be None when offset is set')
if order is not None:
raise TaichiSyntaxError(
'shape cannot be None when order is set')
else:
if isinstance(shape, numbers.Number):
shape = (shape, )
if isinstance(offset, numbers.Number):
offset = (offset, )

if offset is not None:
assert len(shape) == len(
offset
), f'The dimensionality of shape and offset must be the same ({len(shape)} != {len(offset)})'

dim = len(shape)
if offset is not None and dim != len(offset):
raise TaichiSyntaxError(
f'The dimensionality of shape and offset must be the same ({dim} != {len(offset)})'
)
axis_seq = []
shape_seq = []
if order is not None:
if dim != len(order):
raise TaichiSyntaxError(
f'The dimensionality of shape and order must be the same ({dim} != {len(order)})'
)
if dim != len(set(order)):
raise TaichiSyntaxError(
'The axes in order must be different')
for ch in order:
axis = ord(ch) - ord('i')
if axis < 0 or axis >= dim:
raise TaichiSyntaxError(f'Invalid axis {ch}')
axis_seq.append(axis)
shape_seq.append(shape[axis])
else:
axis_seq = list(range(dim))
shape_seq = list(shape)
same_level = order is None
if layout == Layout.SOA:
for e in entries._get_field_members():
impl.root.dense(impl.index_nd(dim),
shape).place(ScalarField(e), offset=offset)
impl._create_snode(axis_seq, shape_seq,
same_level).place(ScalarField(e),
offset=offset)
if needs_grad:
for e in entries_grad._get_field_members():
impl.root.dense(impl.index_nd(dim),
shape).place(ScalarField(e),
offset=offset)
impl._create_snode(axis_seq, shape_seq,
same_level).place(ScalarField(e),
offset=offset)
if needs_dual:
for e in entries_dual._get_field_members():
impl.root.dense(impl.index_nd(dim),
shape).place(ScalarField(e),
offset=offset)
impl._create_snode(axis_seq, shape_seq,
same_level).place(ScalarField(e),
offset=offset)
else:
impl.root.dense(impl.index_nd(dim), shape).place(entries,
offset=offset)
impl._create_snode(axis_seq, shape_seq,
same_level).place(entries, offset=offset)
if needs_grad:
impl.root.dense(impl.index_nd(dim),
shape).place(entries_grad, offset=offset)
impl._create_snode(axis_seq, shape_seq,
same_level).place(entries_grad,
offset=offset)
if needs_dual:
impl.root.dense(impl.index_nd(dim),
shape).place(entries_dual, offset=offset)
impl._create_snode(axis_seq, shape_seq,
same_level).place(entries_dual,
offset=offset)
return entries

@classmethod
Expand Down
39 changes: 26 additions & 13 deletions tests/python/test_offset.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from taichi.lang.misc import get_host_arch_list

import taichi as ti
from tests import test_utils
Expand Down Expand Up @@ -116,26 +117,38 @@ def test():
assert a[i, j][0, 0] == i + j


@test_utils.test()
def test_offset_must_throw_var():
with pytest.raises(AssertionError):
a = ti.field(dtype=ti.float32, shape=3, offset=(3, 4))
b = ti.field(dtype=ti.float32, shape=None, offset=(3, 4))
@test_utils.test(arch=get_host_arch_list())
def test_offset_must_throw_scalar():
with pytest.raises(
ti.TaichiCompilationError,
match='The dimensionality of shape and offset must be the same'):
a = ti.field(dtype=ti.f32, shape=3, offset=(3, 4))
with pytest.raises(ti.TaichiCompilationError,
match='shape cannot be None when offset is set'):
b = ti.field(dtype=ti.f32, shape=None, offset=(3, 4))


@test_utils.test()
@test_utils.test(arch=get_host_arch_list())
def test_offset_must_throw_vector():
with pytest.raises(AssertionError):
a = ti.Vector.field(3, dtype=ti.float32, shape=3, offset=(3, 4))
b = ti.Vector.field(3, dtype=ti.float32, shape=None, offset=(3, ))
with pytest.raises(
ti.TaichiCompilationError,
match='The dimensionality of shape and offset must be the same'):
a = ti.Vector.field(3, dtype=ti.f32, shape=3, offset=(3, 4))
with pytest.raises(ti.TaichiCompilationError,
match='shape cannot be None when offset is set'):
b = ti.Vector.field(3, dtype=ti.f32, shape=None, offset=(3, ))


@test_utils.test()
@test_utils.test(arch=get_host_arch_list())
def test_offset_must_throw_matrix():
with pytest.raises(AssertionError):
c = ti.Matrix.field(3,
with pytest.raises(
ti.TaichiCompilationError,
match='The dimensionality of shape and offset must be the same'):
a = ti.Matrix.field(3,
3,
dtype=ti.i32,
shape=(32, 16, 8),
offset=(32, 16))
d = ti.Matrix.field(3, 3, dtype=ti.i32, shape=None, offset=(32, 16))
with pytest.raises(ti.TaichiCompilationError,
match='shape cannot be None when offset is set'):
b = ti.Matrix.field(3, 3, dtype=ti.i32, shape=None, offset=(32, 16))
Loading