Skip to content

Commit

Permalink
[Lang] Add any()/all() functions for Matrix, move casting ops to comm…
Browse files Browse the repository at this point in the history
…on_ops (#1064)

* [skip ci] add any and all operators to Matrix

* [skip ci] add test

* [skip ci] move __ti_int__ and __ti_float__ to common_ops

* [skip ci] overload builtin any and all ops

* [skip ci] enforce code format

* [skip ci] update doc

Co-authored-by: Kenneth Lozes <klozes@system76-pc.localdomain>
Co-authored-by: Taichi Gardener <taichigardener@gmail.com>
  • Loading branch information
3 people committed May 26, 2020
1 parent 2bf0bee commit 222bc72
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 9 deletions.
4 changes: 3 additions & 1 deletion docs/matrix.rst
Expand Up @@ -13,9 +13,11 @@ Matrices
- ``ti.tr(A)``
- ``ti.determinant(A, type)``
- ``ti.cross(a, b)``, where ``a`` and ``b`` are 3D vectors (i.e. ``3x1`` matrices)
- ``A.cast(type)``
- ``A.cast(type)`` or simply ``int(A)`` and ``float(A)``
- ``R, S = ti.polar_decompose(A, ti.f32)``
- ``U, sigma, V = ti.svd(A, ti.f32)`` (Note that ``sigma`` is a ``3x3`` diagonal matrix)
- ``any(A)``
- ``all(A)``

TODO: doc here better like Vector. WIP

Expand Down
8 changes: 8 additions & 0 deletions python/taichi/lang/common_ops.py
Expand Up @@ -110,3 +110,11 @@ def __invert__(self): # ~a => a.__invert__()
def __not__(self): # not a => a.__not__()
import taichi as ti
return ti.logical_not(self)

def __ti_int__(self):
import taichi as ti
return ti.cast(self, ti.get_runtime().default_ip)

def __ti_float__(self):
import taichi as ti
return ti.cast(self, ti.get_runtime().default_fp)
8 changes: 0 additions & 8 deletions python/taichi/lang/expr.py
Expand Up @@ -223,14 +223,6 @@ def fill(self, val):
from .meta import fill_tensor
fill_tensor(self, val)

def __ti_int__(self):
import taichi as ti
return ti.cast(self, ti.get_runtime().default_ip)

def __ti_float__(self):
import taichi as ti
return ti.cast(self, ti.get_runtime().default_fp)

def parent(self, n=1):
import taichi as ti
p = self.ptr.snode()
Expand Down
14 changes: 14 additions & 0 deletions python/taichi/lang/matrix.py
Expand Up @@ -482,6 +482,20 @@ def min(self):
ret = impl.min(ret, self.entries[i])
return ret

def any(self):
import taichi as ti
ret = (self.entries[0] != ti.expr_init(0))
for i in range(1, len(self.entries)):
ret = ret + (self.entries[i] != ti.expr_init(0))
return -(ret < ti.expr_init(0))

This comment has been minimized.

Copy link
@k-ye

k-ye May 29, 2020

Member

Can you explain why we are comparing if ret < 0? Shouldn't ret always be >= 0? Also why the negation..?

This comment has been minimized.

Copy link
@yuanming-hu

yuanming-hu May 29, 2020

Member

Taichi comparisons gives you 0 for false and -1 for true. This will be fixed once we add the u1 type.

This comment has been minimized.

Copy link
@k-ye

k-ye May 29, 2020

Member

Ha? Is this because of how the LLVM backend is implemented? This test is failing on Metal.. As a workaround, shall I change Metal's comparison result to return -1 for all comparison operators?

This comment has been minimized.

Copy link
@yuanming-hu

yuanming-hu May 29, 2020

Member

Actually some hardware (x86) implements comparisons this way especially when vectorized: https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm256_cmp_&expand=744

I think for now let's make Metal return -1, but in the long run we should support u1...

This comment has been minimized.

Copy link
@k-ye

k-ye May 29, 2020

Member

I guess it's this?

llvm_val[stmt] = builder->CreateSExt(cmp, llvm_type(DataType::i32));

This comment has been minimized.

Copy link
@yuanming-hu

yuanming-hu May 29, 2020

Member

Yes, I used signed extension instead of unsigned to make it consistent with future vectorized versions.

I think the source of all evil is that cmp in llvm gives you i1 but we don't support that in Taichi.

This comment has been minimized.

Copy link
@archibate

archibate May 29, 2020

Collaborator

Interesting, currently OpenGL also return 1 on true, but I didn't see any error in test yet.And I failed the test on OpenGL too.

This comment has been minimized.

Copy link
@k-ye

k-ye May 29, 2020

Member

Interesting, currently OpenGL also return 1 on true, but I didn't see any error in test yet.

Indeed. What happens if you negate 1 to -1 in OpenGL and run the test again?


def all(self):
import taichi as ti
ret = self.entries[0] != ti.expr_init(0)
for i in range(1, len(self.entries)):
ret = ret + (self.entries[i] != ti.expr_init(0))
return -(ret == ti.expr_init(-len(self.entries)))

This comment has been minimized.

Copy link
@k-ye

k-ye May 29, 2020

Member

Same, why all the negations here?


def dot(self, other):
assert self.m == 1 and other.m == 1
return (self.transposed(self) @ other).subscript(0, 0)
Expand Down
10 changes: 10 additions & 0 deletions python/taichi/lang/ops.py
Expand Up @@ -342,6 +342,16 @@ def ti_min(*args):
return ti_min(args[0], ti_min(*args[1:]))


def ti_any(a):
assert hasattr(a, 'any')
return a.any()


def ti_all(a):
assert hasattr(a, 'all')
return a.all()


def append(l, indices, val):
import taichi as ti
a = ti.expr_init(
Expand Down
4 changes: 4 additions & 0 deletions python/taichi/lang/transformer.py
Expand Up @@ -554,6 +554,10 @@ def visit_Call(self, node):
node.func = self.parse_expr('ti.ti_int')
elif func_name == 'float':
node.func = self.parse_expr('ti.ti_float')
elif func_name == 'any':
node.func = self.parse_expr('ti.ti_any')
elif func_name == 'all':
node.func = self.parse_expr('ti.ti_all')
else:
pass
return node
Expand Down
33 changes: 33 additions & 0 deletions tests/python/test_linalg.py
Expand Up @@ -173,3 +173,36 @@ def fill():
assert m2[0][j, i] == int(i + 3 * j + 1)
assert m3[0][i, j] == int(i + 3 * j + 1)
assert m4[0][j, i] == int(i + 3 * j + 1)


@ti.all_archs
def test_any_all():
a = ti.Matrix(2, 2, dt=ti.i32, shape=())
b = ti.var(dt=ti.i32, shape=())

@ti.kernel
def func_any():
b[None] = any(a[None])

@ti.kernel
def func_all():
b[None] = all(a[None])

for i in range(2):
for j in range(2):
a[None][0, 0] = i
a[None][1, 0] = j
a[None][1, 1] = i
a[None][0, 1] = j

func_any()
if i == 1 or j == 1:
assert b[None] == 1
else:
assert b[None] == 0

func_all()
if i == 1 and j == 1:
assert b[None] == 1
else:
assert b[None] == 0

0 comments on commit 222bc72

Please sign in to comment.