def sym_eig2x2(A, dt): """Compute the eigenvalues and right eigenvectors (Av=lambda v) of a 2x2 real symmetric matrix. Mathematical concept refers to https://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix. Args: A (ti.Matrix(2, 2)): input 2x2 symmetric matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. Returns: eigenvalues (ti.Vector(2)): The eigenvalues. Each entry store one eigen value. eigenvectors (ti.Matrix(2, 2)): The eigenvectors. Each column stores one eigenvector. """ tr = A.trace() det = A.determinant() gap = tr**2 - 4 * det lambda1 = (tr + ops.sqrt(gap)) * 0.5 lambda2 = (tr - ops.sqrt(gap)) * 0.5 eigenvalues = Vector([lambda1, lambda2], dt=dt) A1 = A - lambda1 * Matrix.identity(dt, 2) A2 = A - lambda2 * Matrix.identity(dt, 2) v1 = Vector.zero(dt, 2) v2 = Vector.zero(dt, 2) if all(A1 == Matrix.zero(dt, 2, 2)) and all(A1 == Matrix.zero(dt, 2, 2)): v1 = Vector([0.0, 1.0]).cast(dt) v2 = Vector([1.0, 0.0]).cast(dt) else: v1 = Vector([A2[0, 0], A2[1, 0]], dt=dt).normalized() v2 = Vector([A1[0, 0], A1[1, 0]], dt=dt).normalized() eigenvectors = Matrix.cols([v1, v2]) return eigenvalues, eigenvectors
def polar_decompose2d(A, dt): """Perform polar decomposition (A=UP) for 2x2 matrix. Mathematical concept refers to https://en.wikipedia.org/wiki/Polar_decomposition. Args: A (ti.Matrix(2, 2)): input 2x2 matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. Returns: Decomposed 2x2 matrices `U` and `P`. `U` is a 2x2 orthogonal matrix and `P` is a 2x2 positive or semi-positive definite matrix. """ U = Matrix.identity(dt, 2) P = ops.cast(A, dt) zero = ops.cast(0.0, dt) # if A is a zero matrix we simply return the pair (I, A) if (A[0, 0] == zero and A[0, 1] == zero and A[1, 0] == zero and A[1, 1] == zero): pass else: detA = A[0, 0] * A[1, 1] - A[1, 0] * A[0, 1] adetA = abs(detA) B = Matrix([[A[0, 0] + A[1, 1], A[0, 1] - A[1, 0]], [A[1, 0] - A[0, 1], A[1, 1] + A[0, 0]]], dt) if detA < zero: B = Matrix([[A[0, 0] - A[1, 1], A[0, 1] + A[1, 0]], [A[1, 0] + A[0, 1], A[1, 1] - A[0, 0]]], dt) # here det(B) != 0 if A is not the zero matrix adetB = abs(B[0, 0] * B[1, 1] - B[1, 0] * B[0, 1]) k = ops.cast(1.0, dt) / ops.sqrt(adetB) U = B * k P = (A.transpose() @ A + adetA * Matrix.identity(dt, 2)) * k return U, P
def eig2x2(A, dt): """Compute the eigenvalues and right eigenvectors (Av=lambda v) of a 2x2 real matrix. Mathematical concept refers to https://en.wikipedia.org/wiki/Eigendecomposition_of_a_matrix. Args: A (ti.Matrix(2, 2)): input 2x2 matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. Returns: eigenvalues (ti.Matrix(2, 2)): The eigenvalues in complex form. Each row stores one eigenvalue. The first number of the eigenvalue represents the real part and the second number represents the imaginary part. eigenvectors: (ti.Matrix(4, 2)): The eigenvectors in complex form. Each column stores one eigenvector. Each eigenvector consists of 2 entries, each of which is represented by two numbers for its real part and imaginary part. """ tr = A.trace() det = A.determinant() gap = tr**2 - 4 * det lambda1 = Vector.zero(dt, 2) lambda2 = Vector.zero(dt, 2) v1 = Vector.zero(dt, 4) v2 = Vector.zero(dt, 4) if gap > 0: lambda1 = Vector([tr + ops.sqrt(gap), 0.0], dt=dt) * 0.5 lambda2 = Vector([tr - ops.sqrt(gap), 0.0], dt=dt) * 0.5 A1 = A - lambda1[0] * Matrix.identity(dt, 2) A2 = A - lambda2[0] * Matrix.identity(dt, 2) if all(A1 == Matrix.zero(dt, 2, 2)) and all( A1 == Matrix.zero(dt, 2, 2)): v1 = Vector([0.0, 0.0, 1.0, 0.0]).cast(dt) v2 = Vector([1.0, 0.0, 0.0, 0.0]).cast(dt) else: v1 = Vector([A2[0, 0], 0.0, A2[1, 0], 0.0], dt=dt).normalized() v2 = Vector([A1[0, 0], 0.0, A1[1, 0], 0.0], dt=dt).normalized() else: lambda1 = Vector([tr, ops.sqrt(-gap)], dt=dt) * 0.5 lambda2 = Vector([tr, -ops.sqrt(-gap)], dt=dt) * 0.5 A1r = A - lambda1[0] * Matrix.identity(dt, 2) A1i = -lambda1[1] * Matrix.identity(dt, 2) A2r = A - lambda2[0] * Matrix.identity(dt, 2) A2i = -lambda2[1] * Matrix.identity(dt, 2) v1 = Vector([A2r[0, 0], A2i[0, 0], A2r[1, 0], A2i[1, 0]], dt=dt).normalized() v2 = Vector([A1r[0, 0], A1i[0, 0], A1r[1, 0], A1i[1, 0]], dt=dt).normalized() eigenvalues = Matrix.rows([lambda1, lambda2]) eigenvectors = Matrix.cols([v1, v2]) return eigenvalues, eigenvectors
def _randn(dt): ''' Generates a random number from standard normal distribution using the Box-Muller transform. ''' assert dt == f32 or dt == f64 u1 = ops.random(dt) u2 = ops.random(dt) r = ops.sqrt(-2 * ops.log(u1)) c = ops.cos(math.tau * u2) return r * c
def _randn(dt): """ Generate a random float sampled from univariate standard normal (Gaussian) distribution of mean 0 and variance 1, using the Box-Muller transformation. """ assert dt == f32 or dt == f64 u1 = ops.cast(1.0, dt) - ops.random(dt) u2 = ops.random(dt) r = ops.sqrt(-2 * ops.log(u1)) c = ops.cos(math.tau * u2) return r * c
def svd2d(A, dt): """Perform singular value decomposition (A=USV^T) for 2x2 matrix. Mathematical concept refers to https://en.wikipedia.org/wiki/Singular_value_decomposition. Args: A (ti.Matrix(2, 2)): input 2x2 matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. Returns: Decomposed 2x2 matrices `U`, 'S' and `V`. """ R, S = polar_decompose2d(A, dt) c, s = ops.cast(0.0, dt), ops.cast(0.0, dt) s1, s2 = ops.cast(0.0, dt), ops.cast(0.0, dt) if abs(S[0, 1]) < 1e-5: c, s = 1, 0 s1, s2 = S[0, 0], S[1, 1] else: tao = ops.cast(0.5, dt) * (S[0, 0] - S[1, 1]) w = ops.sqrt(tao**2 + S[0, 1]**2) t = ops.cast(0.0, dt) if tao > 0: t = S[0, 1] / (tao + w) else: t = S[0, 1] / (tao - w) c = 1 / ops.sqrt(t**2 + 1) s = -t * c s1 = c**2 * S[0, 0] - 2 * c * s * S[0, 1] + s**2 * S[1, 1] s2 = s**2 * S[0, 0] + 2 * c * s * S[0, 1] + c**2 * S[1, 1] V = Matrix.zero(dt, 2, 2) if s1 < s2: tmp = s1 s1 = s2 s2 = tmp V = Matrix([[-s, c], [-c, -s]], dt=dt) else: V = Matrix([[c, s], [-s, c]], dt=dt) U = R @ V return U, Matrix([[s1, ops.cast(0, dt)], [ops.cast(0, dt), s2]], dt=dt), V
def norm(self, eps=0): """Return the square root of the sum of the absolute squares of its elements. Args: eps (Number): a safe-guard value for sqrt, usually 0. Examples:: a = ti.Vector([3, 4]) a.norm() # sqrt(3*3 + 4*4 + 0) = 5 # `a.norm(eps)` is equivalent to `ti.sqrt(a.dot(a) + eps).` Return: The square root of the sum of the absolute squares of its elements. """ return ops_mod.sqrt(self.norm_sqr() + eps)
def polar_decompose2d(A, dt): """Perform polar decomposition (A=UP) for 2x2 matrix. Mathematical concept refers to https://en.wikipedia.org/wiki/Polar_decomposition. Args: A (ti.Matrix(2, 2)): input 2x2 matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. Returns: Decomposed 2x2 matrices `U` and `P`. """ x, y = A(0, 0) + A(1, 1), A(1, 0) - A(0, 1) scale = (1.0 / ops.sqrt(x * x + y * y)) c = x * scale s = y * scale r = Matrix([[c, -s], [s, c]], dt=dt) return r, r.transpose() @ A
def sym_eig3x3(A, dt): """Compute the eigenvalues and right eigenvectors (Av=lambda v) of a 3x3 real symmetric matrix using Cardano's method. Mathematical concept refers to https://www.mpi-hd.mpg.de/personalhomes/globes/3x3/. Args: A (ti.Matrix(3, 3)): input 3x3 symmetric matrix `A`. dt (DataType): date type of elements in matrix `A`, typically accepts ti.f32 or ti.f64. Returns: eigenvalues (ti.Vector(3)): The eigenvalues. Each entry store one eigen value. eigenvectors (ti.Matrix(3, 3)): The eigenvectors. Each column stores one eigenvector. """ M_SQRT3 = 1.73205080756887729352744634151 m = A.trace() dd = A[0, 1] * A[0, 1] ee = A[1, 2] * A[1, 2] ff = A[0, 2] * A[0, 2] c1 = A[0, 0] * A[1, 1] + A[0, 0] * A[2, 2] + A[1, 1] * A[2, 2] - (dd + ee + ff) c0 = A[2, 2] * dd + A[0, 0] * ee + A[1, 1] * ff - A[0, 0] * A[1, 1] * A[ 2, 2] - 2.0 * A[0, 2] * A[0, 1] * A[1, 2] p = m * m - 3.0 * c1 q = m * (p - 1.5 * c1) - 13.5 * c0 sqrt_p = ops.sqrt(ops.abs(p)) phi = 27.0 * (0.25 * c1 * c1 * (p - c1) + c0 * (q + 6.75 * c0)) phi = (1.0 / 3.0) * ops.atan2(ops.sqrt(ops.abs(phi)), q) c = sqrt_p * ops.cos(phi) s = (1.0 / M_SQRT3) * sqrt_p * ops.sin(phi) eigenvalues = Vector([0.0, 0.0, 0.0], dt=dt) eigenvalues[2] = (1.0 / 3.0) * (m - c) eigenvalues[1] = eigenvalues[2] + s eigenvalues[0] = eigenvalues[2] + c eigenvalues[2] = eigenvalues[2] - s t = ops.abs(eigenvalues[0]) u = ops.abs(eigenvalues[1]) if u > t: t = u u = ops.abs(eigenvalues[2]) if u > t: t = u if t < 1.0: u = t else: u = t * t Q = Matrix.zero(dt, 3, 3) Q[0, 1] = A[0, 1] * A[1, 2] - A[0, 2] * A[1, 1] Q[1, 1] = A[0, 2] * A[0, 1] - A[1, 2] * A[0, 0] Q[2, 1] = A[0, 1] * A[0, 1] Q[0, 0] = Q[0, 1] + A[0, 2] * eigenvalues[0] Q[1, 0] = Q[1, 1] + A[1, 2] * eigenvalues[0] Q[2, 0] = (A[0, 0] - eigenvalues[0]) * (A[1, 1] - eigenvalues[0]) - Q[2, 1] norm = Q[0, 0] * Q[0, 0] + Q[1, 0] * Q[1, 0] + Q[2, 0] * Q[2, 0] norm = ops.sqrt(1.0 / norm) Q[0, 0] *= norm Q[1, 0] *= norm Q[2, 0] *= norm Q[0, 1] = Q[0, 1] + A[0, 2] * eigenvalues[1] Q[1, 1] = Q[1, 1] + A[1, 2] * eigenvalues[1] Q[2, 1] = (A[0, 0] - eigenvalues[1]) * (A[1, 1] - eigenvalues[1]) - Q[2, 1] norm = Q[0, 1] * Q[0, 1] + Q[1, 1] * Q[1, 1] + Q[2, 1] * Q[2, 1] norm = ops.sqrt(1.0 / norm) Q[0, 1] *= norm Q[1, 1] *= norm Q[2, 1] *= norm Q[0, 2] = Q[1, 0] * Q[2, 1] - Q[2, 0] * Q[1, 1] Q[1, 2] = Q[2, 0] * Q[0, 1] - Q[0, 0] * Q[2, 1] Q[2, 2] = Q[0, 0] * Q[1, 1] - Q[1, 0] * Q[0, 1] return eigenvalues, Q
def norm(self, eps=0): return ops_mod.sqrt(self.norm_sqr() + eps)