def inverse(self): """The inverse of a matrix. Note: The matrix dimension should be less than or equal to 4. Returns: The inverse of a matrix. Raises: Exception: Inversions of matrices with sizes >= 5 are not supported. """ assert self.n == self.m, 'Only square matrices are invertible' if self.n == 1: return Matrix([1 / self(0, 0)]) elif self.n == 2: inv_det = impl.expr_init(1.0 / self.determinant()) # Discussion: https://github.com/taichi-dev/taichi/pull/943#issuecomment-626344323 return inv_det * Matrix([[self(1, 1), -self(0, 1)], [-self(1, 0), self(0, 0)]]).variable() elif self.n == 3: n = 3 inv_determinant = impl.expr_init(1.0 / self.determinant()) entries = [[0] * n for _ in range(n)] def E(x, y): return self(x % n, y % n) for i in range(n): for j in range(n): entries[j][i] = impl.expr_init( inv_determinant * (E(i + 1, j + 1) * E(i + 2, j + 2) - E(i + 2, j + 1) * E(i + 1, j + 2))) return Matrix(entries) elif self.n == 4: n = 4 inv_determinant = impl.expr_init(1.0 / self.determinant()) entries = [[0] * n for _ in range(n)] def E(x, y): return self(x % n, y % n) for i in range(n): for j in range(n): entries[j][i] = impl.expr_init( inv_determinant * (-1)**(i + j) * ((E(i + 1, j + 1) * (E(i + 2, j + 2) * E(i + 3, j + 3) - E(i + 3, j + 2) * E(i + 2, j + 3)) - E(i + 2, j + 1) * (E(i + 1, j + 2) * E(i + 3, j + 3) - E(i + 3, j + 2) * E(i + 1, j + 3)) + E(i + 3, j + 1) * (E(i + 1, j + 2) * E(i + 2, j + 3) - E(i + 2, j + 2) * E(i + 1, j + 3))))) return Matrix(entries) else: raise Exception( "Inversions of matrices with sizes >= 5 are not supported")
def build_assign_annotated(ctx, target, value, is_static_assign, annotation): """Build an annotated assginment like this: target: annotation = value. Args: ctx (ast_builder_utils.BuilderContext): The builder context. target (ast.Name): A variable name. `target.id` holds the name as a string. annotation: A type we hope to assign to the target value: A node representing the value. is_static_assign: A boolean value indicating whether this is a static assignment """ is_local = isinstance(target, ast.Name) anno = impl.expr_init(annotation) if is_static_assign: raise TaichiSyntaxError( "Static assign cannot be used on annotated assignment") if is_local and not ctx.is_var_declared(target.id): var = ti_ops.cast(value, anno) var = impl.expr_init(var) ctx.create_variable(target.id, var) else: var = build_stmt(ctx, target) if var.ptr.get_ret_type() != anno: raise TaichiSyntaxError( "Static assign cannot have type overloading") var.assign(value) return var
def svd3d(A, dt, iters=None): assert A.n == 3 and A.m == 3 inputs = tuple([e.ptr for e in A.entries]) assert dt in [ti.f32, ti.f64] if iters is None: if dt == ti.f32: iters = 5 else: iters = 8 if dt == ti.f32: rets = _ti_core.sifakis_svd_f32(*inputs, iters) else: rets = _ti_core.sifakis_svd_f64(*inputs, iters) assert len(rets) == 21 U_entries = rets[:9] V_entries = rets[9:18] sig_entries = rets[18:] U = expr_init(ti.Matrix.zero(dt, 3, 3)) V = expr_init(ti.Matrix.zero(dt, 3, 3)) sigma = expr_init(ti.Matrix.zero(dt, 3, 3)) for i in range(3): for j in range(3): U(i, j).assign(U_entries[i * 3 + j]) V(i, j).assign(V_entries[i * 3 + j]) sigma(i, i).assign(sig_entries[i]) return U, sigma, V
def build_ndrange_for(ctx, node): with ctx.variable_scope_guard(): ndrange_var = impl.expr_init(build_stmt(ctx, node.iter)) ndrange_begin = ti_ops.cast(expr.Expr(0), primitive_types.i32) ndrange_end = ti_ops.cast( expr.Expr(impl.subscript(ndrange_var.acc_dimensions, 0)), primitive_types.i32) ndrange_loop_var = expr.Expr(_ti_core.make_id_expr('')) _ti_core.begin_frontend_range_for(ndrange_loop_var.ptr, ndrange_begin.ptr, ndrange_end.ptr) I = impl.expr_init(ndrange_loop_var) targets = ASTTransformer.get_for_loop_targets(node) for i, target in enumerate(targets): if i + 1 < len(targets): target_tmp = impl.expr_init( I // ndrange_var.acc_dimensions[i + 1]) else: target_tmp = impl.expr_init(I) ctx.create_variable( target, impl.expr_init(target_tmp + impl.subscript( impl.subscript(ndrange_var.bounds, i), 0))) if i + 1 < len(targets): I.assign(I - target_tmp * ndrange_var.acc_dimensions[i + 1]) build_stmts(ctx, node.body) _ti_core.end_frontend_range_for() return None
def store(self, index, value): args_group = () if self.num_dims == 1: args_group = impl.make_expr_group(index.x, value.r, value.g, value.b, value.a) elif self.num_dims == 2: args_group = impl.make_expr_group(index.x, index.y, value.r, value.g, value.b, value.a) elif self.num_dims == 3: args_group = impl.make_expr_group(index.x, index.y, index.z, value.r, value.g, value.b, value.a) impl.expr_init( _ti_core.make_texture_op_expr(_ti_core.TextureOpType.kStore, self.ptr_expr, args_group))
def atomic_sub(x, y): """Atomically subtract `x` by `y`, store the result in `x`, and return the old value of `x`. `x` must be a writable target, constant expressions or scalars are not allowed. Args: x, y (Union[:mod:`~taichi.types.primitive_types`, :class:`~taichi.Matrix`]): \ The input. Returns: The old value of `x`. Example:: >>> @ti.kernel >>> def test(): >>> x = ti.Vector([0, 0, 0]) >>> y = ti.Vector([1, 2, 3]) >>> z = ti.atomic_sub(x, y) >>> print(x) # [-1, -2, -3] the new value of x >>> print(z) # [0, 0, 0], the old value of x >>> >>> ti.atomic_sub(1, x) # will raise TaichiSyntaxError """ return impl.expr_init( expr.Expr(_ti_core.expr_atomic_sub(x.ptr, y.ptr), tb=stack_info()))
def build_IfExp(ctx, node): build_stmt(ctx, node.test) build_stmt(ctx, node.body) build_stmt(ctx, node.orelse) if is_taichi_class(node.test.ptr) or is_taichi_class( node.body.ptr) or is_taichi_class(node.orelse.ptr): node.ptr = ti_ops.select(node.test.ptr, node.body.ptr, node.orelse.ptr) return node.ptr is_static_if = (ASTTransformer.get_decorator(ctx, node.test) == "static") if is_static_if: if node.test.ptr: node.ptr = build_stmt(ctx, node.body) else: node.ptr = build_stmt(ctx, node.orelse) return node.ptr val = impl.expr_init(None) impl.begin_frontend_if(node.test.ptr) _ti_core.begin_frontend_if_true() val.assign(node.body.ptr) _ti_core.pop_scope() _ti_core.begin_frontend_if_false() val.assign(node.orelse.ptr) _ti_core.pop_scope() node.ptr = val return node.ptr
def atomic_xor(x, y): """Atomically compute the bit-wise XOR of `x` and `y`, element-wise. Store the result in `x`, and return the old value of `x`. `x` must be a writable target, constant expressions or scalars are not allowed. Args: x, y (Union[:mod:`~taichi.types.primitive_types`, :class:`~taichi.Matrix`]): \ The input. When both are matrices they must have the same shape. Returns: The old value of `x`. Example:: >>> @ti.kernel >>> def test(): >>> x = ti.Vector([-1, 0, 1]) >>> y = ti.Vector([1, 2, 3]) >>> z = ti.atomic_xor(x, y) >>> print(x) # [-2, 2, 2] the new value of x >>> print(z) # [-1, 0, 1], the old value of x >>> >>> ti.atomic_xor(1, x) # will raise TaichiSyntaxError """ return impl.expr_init( expr.Expr(_ti_core.expr_atomic_bit_xor(x.ptr, y.ptr), tb=stack_info()))
def build_assign_basic(ctx, target, value, is_static_assign): """Build basic assginment like this: target = value. Args: ctx (ast_builder_utils.BuilderContext): The builder context. target (ast.Name): A variable name. `target.id` holds the name as a string. value: A node representing the value. is_static_assign: A boolean value indicating whether this is a static assignment """ is_local = isinstance(target, ast.Name) if is_static_assign: if not is_local: raise TaichiSyntaxError( "Static assign cannot be used on elements in arrays") ctx.create_variable(target.id, value) var = value elif is_local and not ctx.is_var_declared(target.id): var = impl.expr_init(value) ctx.create_variable(target.id, var) else: var = build_stmt(ctx, target) try: var.assign(value) except AttributeError: raise TaichiSyntaxError( f"Variable '{unparse(target).strip()}' cannot be assigned. Maybe it is not a Taichi object?" ) return var
def atomic_max(x, y): """Atomically compute the maximum of `x` and `y`, element-wise. Store the result in `x`, and return the old value of `x`. `x` must be a writable target, constant expressions or scalars are not allowed. Args: x, y (Union[:mod:`~taichi.types.primitive_types`, :class:`~taichi.Matrix`]): \ The input. Returns: The old value of `x`. Example:: >>> @ti.kernel >>> def test(): >>> x = 1 >>> y = 2 >>> z = ti.atomic_max(x, y) >>> print(x) # 2 the new value of x >>> print(z) # 1, the old value of x >>> >>> ti.atomic_max(1, x) # will raise TaichiSyntaxError """ return impl.expr_init( expr.Expr(_ti_core.expr_atomic_max(x.ptr, y.ptr), tb=stack_info()))
def determinant(a): if a.n == 2 and a.m == 2: return a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0) elif a.n == 3 and a.m == 3: return a(0, 0) * (a(1, 1) * a(2, 2) - a(2, 1) * a(1, 2)) - a( 1, 0) * (a(0, 1) * a(2, 2) - a(2, 1) * a(0, 2)) + a( 2, 0) * (a(0, 1) * a(1, 2) - a(1, 1) * a(0, 2)) elif a.n == 4 and a.m == 4: n = 4 def E(x, y): return a(x % n, y % n) det = impl.expr_init(0.0) for i in range(4): det = det + (-1.0)**i * ( a(i, 0) * (E(i + 1, 1) * (E(i + 2, 2) * E(i + 3, 3) - E(i + 3, 2) * E(i + 2, 3)) - E(i + 2, 1) * (E(i + 1, 2) * E(i + 3, 3) - E(i + 3, 2) * E(i + 1, 3)) + E(i + 3, 1) * (E(i + 1, 2) * E(i + 2, 3) - E(i + 2, 2) * E(i + 1, 3)))) return det else: raise Exception( "Determinants of matrices with sizes >= 5 are not supported")
def solve(A, b, dt=None): """Solve a matrix using Gauss elimination method. Args: A (ti.Matrix(n, n)): input nxn matrix `A`. b (ti.Vector(n, 1)): input nx1 vector `b`. dt (DataType): The datatype for the `A` and `b`. Returns: x (ti.Vector(n, 1)): the solution of Ax=b. """ assert A.n == A.m, "Only sqaure matrix is supported" assert A.n >= 2 and A.n <= 3, "Only 2D and 3D matrices are supported" assert A.m == b.n, "Matrix and Vector dimension dismatch" if dt is None: dt = impl.get_runtime().default_fp nrow, ncol = static(A.n, A.n + 1) Ab = expr_init(Matrix.zero(dt, nrow, ncol)) lhs = tuple([e.ptr for e in A.entries]) rhs = tuple([e.ptr for e in b.entries]) for i in range(nrow): for j in range(nrow): Ab(i, j)._assign(lhs[nrow * i + j]) for i in range(nrow): Ab(i, nrow)._assign(rhs[i]) if A.n == 2: return _gauss_elimination_2x2(Ab, dt) if A.n == 3: return _gauss_elimination_3x3(Ab, dt) raise Exception("Solver only supports 2D and 3D matrices.")
def variable(self): ret = self.copy() ret.entries = { k: impl.expr_init(v) if isinstance(v, (numbers.Number, expr.Expr)) else v.variable() for k, v in ret.items } return ret
def append(node, indices, val): """Append a value `val` to a SNode `node` at index `indices`. Args: node (:class:`~taichi.SNode`): Input SNode. indices (Union[int, :class:`~taichi.Vector`]): the indices to visit. val (:mod:`~taichi.types`): the data to be appended. """ a = impl.expr_init( _ti_core.insert_append(node._snode.ptr, expr.make_expr_group(indices), expr.Expr(val).ptr)) return a
def random(dtype=float): """The random function. Args: dtype (DataType): Type of the random variable. Returns: A random variable whose type is `dtype`. """ dtype = cook_dtype(dtype) x = expr.Expr(_ti_core.make_rand_expr(dtype)) return impl.expr_init(x)
def svd3d(A, dt, iters=None): """Perform singular value decomposition (A=USV^T) for 3x3 matrix. Mathematical concept refers to https://en.wikipedia.org/wiki/Singular_value_decomposition. Args: A (ti.Matrix(3, 3)): input 3x3 matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. iters (int): iteration number to control algorithm precision. Returns: Decomposed 3x3 matrices `U`, 'S' and `V`. """ assert A.n == 3 and A.m == 3 inputs = tuple([e.ptr for e in A.entries]) assert dt in [f32, f64] if iters is None: if dt == f32: iters = 5 else: iters = 8 if dt == f32: rets = get_runtime().prog.current_ast_builder().sifakis_svd_f32( *inputs, iters) else: rets = get_runtime().prog.current_ast_builder().sifakis_svd_f64( *inputs, iters) assert len(rets) == 21 U_entries = rets[:9] V_entries = rets[9:18] sig_entries = rets[18:] U = expr_init(Matrix.zero(dt, 3, 3)) V = expr_init(Matrix.zero(dt, 3, 3)) sigma = expr_init(Matrix.zero(dt, 3, 3)) for i in range(3): for j in range(3): U(i, j)._assign(U_entries[i * 3 + j]) V(i, j)._assign(V_entries[i * 3 + j]) sigma(i, i)._assign(sig_entries[i]) return U, sigma, V
def build_grouped_ndrange_for(ctx, node): with ctx.variable_scope_guard(): ndrange_var = impl.expr_init(build_stmt(ctx, node.iter.args[0])) ndrange_begin = ti_ops.cast(expr.Expr(0), primitive_types.i32) ndrange_end = ti_ops.cast( expr.Expr(impl.subscript(ndrange_var.acc_dimensions, 0)), primitive_types.i32) ndrange_loop_var = expr.Expr(_ti_core.make_id_expr('')) _ti_core.begin_frontend_range_for(ndrange_loop_var.ptr, ndrange_begin.ptr, ndrange_end.ptr) targets = ASTTransformer.get_for_loop_targets(node) if len(targets) != 1: raise TaichiSyntaxError( f"Group for should have 1 loop target, found {len(targets)}" ) target = targets[0] target_var = impl.expr_init( matrix.Vector([0] * len(ndrange_var.dimensions), dt=primitive_types.i32)) ctx.create_variable(target, target_var) I = impl.expr_init(ndrange_loop_var) for i in range(len(ndrange_var.dimensions)): if i + 1 < len(ndrange_var.dimensions): target_tmp = I // ndrange_var.acc_dimensions[i + 1] else: target_tmp = I impl.subscript(target_var, i).assign(target_tmp + ndrange_var.bounds[i][0]) if i + 1 < len(ndrange_var.dimensions): I.assign(I - target_tmp * ndrange_var.acc_dimensions[i + 1]) build_stmts(ctx, node.body) _ti_core.end_frontend_range_for() return None
def build_Assign(ctx, node): build_stmt(ctx, node.value) is_static_assign = isinstance( node.value, ast.Call) and node.value.func.ptr is impl.static # Keep all generated assign statements and compose single one at last. # The variable is introduced to support chained assignments. # Ref https://github.com/taichi-dev/taichi/issues/2659. values = node.value.ptr if is_static_assign else impl.expr_init( node.value.ptr) for node_target in node.targets: ASTTransformer.build_assign_unpack(ctx, node_target, values, is_static_assign) return None
def __init__(self, *args, **kwargs): # converts lists to matrices and dicts to structs if len(args) == 1 and kwargs == {} and isinstance(args[0], dict): self.entries = args[0] elif len(args) == 0: self.entries = kwargs else: raise TaichiSyntaxError( "Custom structs need to be initialized using either dictionary or keyword arguments" ) for k, v in self.entries.items(): if isinstance(v, (list, tuple)): v = Matrix(v) if isinstance(v, dict): v = Struct(v) self.entries[k] = v if in_python_scope() else impl.expr_init(v) self._register_members()
def build_short_circuit_and(ast_builder, operands): if len(operands) == 1: return operands[0].ptr val = impl.expr_init(None) lhs = operands[0].ptr impl.begin_frontend_if(ast_builder, lhs) ast_builder.begin_frontend_if_true() rhs = ASTTransformer.build_short_circuit_and(ast_builder, operands[1:]) val.assign(rhs) ast_builder.pop_scope() ast_builder.begin_frontend_if_false() val.assign(0) ast_builder.pop_scope() return val
def build_short_circuit_or(operands): if len(operands) == 1: return operands[0].ptr val = impl.expr_init(None) lhs = operands[0].ptr impl.begin_frontend_if(lhs) _ti_core.begin_frontend_if_true() val.assign(1) _ti_core.pop_scope() _ti_core.begin_frontend_if_false() rhs = ASTTransformer.build_short_circuit_or(operands[1:]) val.assign(rhs) _ti_core.pop_scope() return val
def determinant(a): """Get the determinant of a matrix. Note: The matrix dimension should be less than or equal to 4. Returns: The determinant of a matrix. Raises: Exception: Determinants of matrices with sizes >= 5 are not supported. """ if a.n == 2 and a.m == 2: return a(0, 0) * a(1, 1) - a(0, 1) * a(1, 0) elif a.n == 3 and a.m == 3: return a(0, 0) * (a(1, 1) * a(2, 2) - a(2, 1) * a(1, 2)) - a( 1, 0) * (a(0, 1) * a(2, 2) - a(2, 1) * a(0, 2)) + a( 2, 0) * (a(0, 1) * a(1, 2) - a(1, 1) * a(0, 2)) elif a.n == 4 and a.m == 4: n = 4 def E(x, y): return a(x % n, y % n) det = impl.expr_init(0.0) for i in range(4): det = det + (-1.0)**i * ( a(i, 0) * (E(i + 1, 1) * (E(i + 2, 2) * E(i + 3, 3) - E(i + 3, 2) * E(i + 2, 3)) - E(i + 2, 1) * (E(i + 1, 2) * E(i + 3, 3) - E(i + 3, 2) * E(i + 1, 3)) + E(i + 3, 1) * (E(i + 1, 2) * E(i + 2, 3) - E(i + 2, 2) * E(i + 1, 3)))) return det else: raise Exception( "Determinants of matrices with sizes >= 5 are not supported")
def build_IfExp(ctx, node): build_stmt(ctx, node.test) build_stmt(ctx, node.body) build_stmt(ctx, node.orelse) if is_taichi_class(node.test.ptr) or is_taichi_class( node.body.ptr) or is_taichi_class(node.orelse.ptr): node.ptr = ti_ops.select(node.test.ptr, node.body.ptr, node.orelse.ptr) warnings.warn_explicit( 'Using conditional expression for element-wise select operation on ' 'Taichi vectors/matrices is deprecated. ' 'Please use "ti.select" instead.', DeprecationWarning, ctx.file, node.lineno + ctx.lineno_offset) return node.ptr is_static_if = (ASTTransformer.get_decorator(ctx, node.test) == "static") if is_static_if: if node.test.ptr: node.ptr = build_stmt(ctx, node.body) else: node.ptr = build_stmt(ctx, node.orelse) return node.ptr val = impl.expr_init(None) impl.begin_frontend_if(ctx.ast_builder, node.test.ptr) ctx.ast_builder.begin_frontend_if_true() val.assign(node.body.ptr) ctx.ast_builder.pop_scope() ctx.ast_builder.begin_frontend_if_false() val.assign(node.orelse.ptr) ctx.ast_builder.pop_scope() node.ptr = val return node.ptr
def random(dtype=float): """Return a single random float/integer according to the specified data type. Must be called in taichi scope. If the required `dtype` is float type, this function returns a random number sampled from the uniform distribution in the half-open interval [0, 1). For integer types this function returns a random integer in the half-open interval [0, 2^32) if a 32-bit integer is required, or a random integer in the half-open interval [0, 2^64) if a 64-bit integer is required. Args: dtype (:mod:`~taichi.types.primitive_types`): Type of the required random value. Returns: A random value with type `dtype`. Example:: >>> @ti.kernel >>> def test(): >>> x = ti.random(float) >>> print(x) # 0.090257 >>> >>> y = ti.random(ti.f64) >>> print(y) # 0.716101627301 >>> >>> i = ti.random(ti.i32) >>> print(i) # -963722261 >>> >>> j = ti.random(ti.i64) >>> print(j) # 73412986184350777 """ dtype = cook_dtype(dtype) x = expr.Expr(_ti_core.make_rand_expr(dtype)) return impl.expr_init(x)
def variable(self): ret = self.copy() ret.entries = [impl.expr_init(e) for e in ret.entries] return ret
def append(l, indices, val): a = impl.expr_init( _ti_core.insert_append(l.snode.ptr, expr.make_expr_group(indices), expr.Expr(val).ptr)) return a
def atomic_xor(a, b): return impl.expr_init( expr.Expr(_ti_core.expr_atomic_bit_xor(a.ptr, b.ptr), tb=stack_info()))
def random(dtype=float): dtype = cook_dtype(dtype) x = Expr(_ti_core.make_rand_expr(dtype)) return impl.expr_init(x)
def atomic_max(a, b): return impl.expr_init( Expr(_ti_core.expr_atomic_max(a.ptr, b.ptr), tb=stack_info()))
def __init__(self, n=1, m=1, dt=None, shape=None, offset=None, empty=False, layout=None, needs_grad=False, keep_raw=False, rows=None, cols=None): self.grad = None # construct from rows or cols (deprecated) if rows is not None or cols is not None: warning( f"ti.Matrix(rows=[...]) or ti.Matrix(cols=[...]) is deprecated, use ti.Matrix.rows([...]) or ti.Matrix.cols([...]) instead.", DeprecationWarning, stacklevel=2) if rows is not None and cols is not None: raise Exception("cannot specify both rows and columns") self.dt = dt mat = Matrix.cols(cols) if cols is not None else Matrix.rows(rows) self.n = mat.n self.m = mat.m self.entries = mat.entries return elif empty == True: warning( f"ti.Matrix(n, m, empty=True) is deprecated, use ti.Matrix.empty(n, m) instead", DeprecationWarning, stacklevel=2) self.dt = dt self.entries = [[None] * m for _ in range(n)] return elif isinstance(n, (list, tuple, np.ndarray)): if len(n) == 0: mat = [] elif isinstance(n[0], Matrix): raise Exception( 'cols/rows required when using list of vectors') elif not isinstance(n[0], Iterable): if impl.inside_kernel(): # wrap potential constants with Expr if keep_raw: mat = [list([x]) for x in n] else: mat = [list([expr.Expr(x)]) for x in n] else: mat = [[x] for x in n] else: mat = [list(r) for r in n] self.n = len(mat) if len(mat) > 0: self.m = len(mat[0]) else: self.m = 1 self.entries = [x for row in mat for x in row] else: if dt is None: # create a local matrix with specific (n, m) self.entries = [impl.expr_init(None) for i in range(n * m)] self.n = n self.m = m else: # construct global matrix (deprecated) warning( "Declaring global matrices using `ti.Matrix(n, m, dt, shape)` is deprecated, " "use `ti.Matrix.field(n, m, dtype, shape)` instead", DeprecationWarning, stacklevel=2) mat = Matrix.field(n=n, m=m, dtype=dt, shape=shape, offset=offset, needs_grad=needs_grad, layout=layout) self.n = mat.n self.m = mat.m self.entries = mat.entries self.grad = mat.grad if self.n * self.m > 32: warning( f'Taichi matrices/vectors with {self.n}x{self.m} > 32 entries are not suggested.' ' Matrices/vectors will be automatically unrolled at compile-time for performance.' ' So the compilation time could be extremely long if the matrix size is too big.' ' You may use a field to store a large matrix like this, e.g.:\n' f' x = ti.field(ti.f32, ({self.n}, {self.m})).\n' ' See https://taichi.readthedocs.io/en/stable/tensor_matrix.html#matrix-size' ' for more details.', UserWarning, stacklevel=2)