# Operators

Here we present the supported operators in Taichi for both primitive types and compound types such as matrices.

## Supported operators for primitive types

### Arithmetic operators

Operation | Result |
---|---|

`-a` | `a` negated |

`+a` | `a` unchanged |

`a + b` | sum of `a` and `b` |

`a - b` | difference of `a` and `b` |

`a * b` | product of `a` and `b` |

`a / b` | quotient of `a` and `b` |

`a // b` | floored quotient of `a` and `b` |

`a % b` | remainder of `a / b` |

`a ** b` | `a` to the power of `b` |

##### note

The `%`

operator in Taichi follows the Python style instead of C style, e.g.,

`# In Taichi-scope or Python-scope:`

print(2 % 3) # 2

print(-2 % 3) # 1

For C-style mod (`%`

), please use `ti.raw_mod`

. This function also receives floating points as arguments.

`ti.raw_mod(a, b)`

returns `a - b * int(float(a) / b)`

.

`print(ti.raw_mod(2, 3)) # 2`

print(ti.raw_mod(-2, 3)) # -2

print(ti.raw_mod(3.5, 1.5)) # 0.5

##### note

Python3 distinguishes `/`

(true division) and `//`

(floor division), e.g., `1.0 / 2.0 = 0.5`

, `1 / 2 = 0.5`

, `1 // 2 = 0`

, `4.2 // 2 = 2`

. Taichi follows the same design:

**True divisions**on integral types first cast their operands to the default floating point type.**Floor divisions**on floating point types first cast their operands to the default integral type.

To avoid such implicit casting, you can manually cast your operands to desired types, using `ti.cast`

. Please see Default precisions for more details on default numerical types.

Taichi also provides `ti.raw_div`

function which performs true division if one of the operands is floating point type and performs floor division if both operands are integral types.

`print(ti.raw_div(5, 2)) # 2`

print(ti.raw_div(5, 2.0)) # 2.5

### Comparison operators

Operation | Result |
---|---|

`a == b` | if `a` is equal to `b` , then True, else False |

`a != b` | if `a` is not equal to `b` , then True, else False |

`a > b` | if `a` is strictly greater than `b` , then True, else False |

`a < b` | if `a` is strictly less than `b` , then True, else False |

`a >= b` | if `a` is greater than or equal to `b` , then True, else False |

`a <= b` | if `a` is less than or equal to `b` , then True, else False |

### Logical operators

Operation | Result |
---|---|

`not a` | if `a` is False, then True, else False |

`a or b` | if `a` is False, then `b` , else `a` |

`a and b` | if `a` is False, then `a` , else `b` |

### Conditional operations

The result of conditional expression `a if cond else b`

is `a`

if `cond`

is True, or `b`

otherwise. `a`

and `b`

must have a same type.

The conditional expression does short-circuit evaluation, which means the branch not chosen is not evaluated.

`a = ti.field(ti.i32, shape=(10,))`

for i in range(10):

a[i] = i

@ti.kernel

def cond_expr(ind: ti.i32) -> ti.i32:

return a[ind] if ind < 10 else 0

cond_expr(3) # returns 3

cond_expr(10) # returns 0, a[10] is not evaluated

For element-wise conditional operations on Taichi vectors and matrices, Taichi provides `ti.select(cond, a, b)`

which **does not** do short-circuit evaluation.

`cond = ti.Vector([1, 0])`

a = ti.Vector([2, 3])

b = ti.Vector([4, 5])

ti.select(cond, a, b) # ti.Vector([2, 5])

### Bitwise operators

Operation | Result |
---|---|

`~a` | the bits of `a` inverted |

`a & b` | bitwise and of `a` and `b` |

`a ^ b` | bitwise exclusive or of `a` and `b` |

`a | b` | bitwise or of `a` and `b` |

`a << b` | left-shift `a` by `b` bits |

`a >> b` | right-shift `a` by `b` bits |

##### note

The `>>`

operation denotes the Shift Arithmetic Right (SAR) operation. For the Shift Logical Right (SHR) operation, consider using `ti.bit_shr()`

. For left shift operations, SAL and SHL are the same.

### Trigonometric functions

`ti.sin(x)`

ti.cos(x)

ti.tan(x)

ti.asin(x)

ti.acos(x)

ti.atan2(x, y)

ti.tanh(x)

### Other arithmetic functions

`ti.sqrt(x)`

ti.rsqrt(x) # A fast version for `1 / ti.sqrt(x)`.

ti.exp(x)

ti.log(x)

ti.round(x, dtype=None)

ti.floor(x, dtype=None)

ti.ceil(x, dtype=None)

ti.sum(x)

ti.max(x, y, ...)

ti.min(x, y, ...)

ti.abs(x) # Same as `abs(x)`

ti.pow(x, y) # Same as `pow(x, y)` and `x ** y`

The `dtype`

argument in `round`

, `floor`

and `ceil`

functions specifies the data type of the returned value. The default `None`

means the returned type is the same as input `x`

.

### Builtin-alike functions

`abs(x) # Same as `ti.abs(x, y)``

pow(x, y) # Same as `ti.pow(x, y)` and `x ** y`.

### Random number generator

`ti.random(dtype=float)`

##### note

`ti.random`

supports `u32`

, `i32`

, `u64`

, `i64`

, and all floating point types. The range of the returned value is type-specific.

Type | Range |
---|---|

i32 | -2,147,483,648 to 2,147,483,647 |

u32 | 0 to 4,294,967,295 |

i64 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |

u64 | 0 to 18,446,744,073,709,551,615 |

floating point | 0.0 to 1.0 |

### Supported atomic operations

In Taichi, augmented assignments (e.g., `x[i] += 1`

) are automatically atomic.

##### caution

When modifying global variables in parallel, make sure you use atomic operations. For example, to sum up all the elements in `x`

,

`@ti.kernel`

def sum():

for i in x:

# Approach 1: OK

total[None] += x[i]

# Approach 2: OK

ti.atomic_add(total[None], x[i])

# Approach 3: Wrong result since the operation is not atomic.

total[None] = total[None] + x[i]

##### note

When atomic operations are applied to local values, the Taichi compiler will try to demote these operations into their non-atomic counterparts.

Apart from the augmented assignments, explicit atomic operations, such as `ti.atomic_add`

, also do read-modify-write atomically. These operations additionally return the **old value** of the first argument. For example,

`x[i] = 3`

y[i] = 4

z[i] = ti.atomic_add(x[i], y[i])

# now x[i] = 7, y[i] = 4, z[i] = 3

Below is a list of all explicit atomic operations:

Operation | Behavior |
---|---|

`ti.atomic_add(x, y)` | atomically compute `x + y` , store the result in `x` , and return the old value of `x` |

`ti.atomic_sub(x, y)` | atomically compute `x - y` , store the result in `x` , and return the old value of `x` |

`ti.atomic_and(x, y)` | atomically compute `x & y` , store the result in `x` , and return the old value of `x` |

`ti.atomic_or(x, y)` | atomically compute `x | y` , store the result in `x` , and return the old value of `x` |

`ti.atomic_xor(x, y)` | atomically compute `x ^ y` , store the result in `x` , and return the old value of `x` |

`ti.atomic_max(x, y)` | atomically compute `max(x, y)` , store the result in `x` , and return the old value of `x` |

`ti.atomic_min(x, y)` | atomically compute `min(x, y)` , store the result in `x` , and return the old value of `x` |

##### note

Supported atomic operations on each backend:

type | CPU | CUDA | OpenGL | Metal | C source |
---|---|---|---|---|---|

i32 | OK | OK | OK | OK | OK |

f32 | OK | OK | OK | OK | OK |

i64 | OK | OK | ⭕ | ❌ | OK |

f64 | OK | OK | ⭕ | ❌ | OK |

(⭕ Requiring extensions for the backend.)

## Supported operators for matrices

The previously mentioned operations on primitive types can also be applied on compound types such as matrices. In these cases, they are applied in an element-wise manner. For example:

`B = ti.Matrix([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])`

C = ti.Matrix([[3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])

A = ti.sin(B)

# is equivalent to

for i in ti.static(range(2)):

for j in ti.static(range(3)):

A[i, j] = ti.sin(B[i, j])

A = B ** 2

# is equivalent to

for i in ti.static(range(2)):

for j in ti.static(range(3)):

A[i, j] = B[i, j] ** 2

A = B ** C

# is equivalent to

for i in ti.static(range(2)):

for j in ti.static(range(3)):

A[i, j] = B[i, j] ** C[i, j]

A += 2

# is equivalent to

for i in ti.static(range(2)):

for j in ti.static(range(3)):

A[i, j] += 2

A += B

# is equivalent to

for i in ti.static(range(2)):

for j in ti.static(range(3)):

A[i, j] += B[i, j]

In addition, the following methods are supported matrices operations:

`a = ti.Matrix([[2, 3], [4, 5]])`

a.transpose() # the transposed matrix of `a`, will not effect the data in `a`.

a.trace() # the trace of matrix `a`, the returned scalar value can be computed as `a[0, 0] + a[1, 1] + ...`.

a.determinant() # the determinant of matrix `a`.

a.inverse() # (ti.Matrix) the inverse of matrix `a`.

a@a # @ denotes matrix multiplication

##### note

For now, determinant() and inverse() only works in Taichi-scope, and the size of the matrix must be 1x1, 2x2, 3x3 or 4x4.