Exemplo n.º 1
0
def mainImage(iMouse: ti.Vector, iTime: ti.f32, i: ti.i32,
              j: ti.i32) -> ti.Vector:
    fragCoord = ti.Vector([i, j])

    p = -1.0 + 2.0 * fragCoord / iResolution
    m = -1.0 + 2.0 * iMouse  # iMouse 已经归一化

    a1 = ti.atan2(p[1] - m[1], p[0] - m[0])
    a2 = ti.atan2(p[1] + m[1], p[0] + m[0])
    r1 = ti.sqrt((p - m).dot(p - m))
    r2 = ti.sqrt((p + m).dot(p + m))

    uv = ti.Vector(
        [0.2 * iTime + (r1 - r2) * 0.25,
         ti.asin(ti.sin(a1 - a2)) / 3.1416])

    w = ti.exp(-15.0 * r1 * r1) + ti.exp(-15.0 * r2 * r2)
    w += 0.25 * smoothstep(0.93, 1.0, ti.sin(128.0 * uv[0]))
    w += 0.25 * smoothstep(0.93, 1.0, ti.sin(128.0 * uv[1]))

    # 可以使用纹理
    # vec3 col = texture( iChannel0, 0.125*uv ).zyx;
    col = ti.Vector([0.0, 0.0, 0.0])
    fragColor = col + w
    return fragColor
Exemplo n.º 2
0
def test_atan2_f64():
    grad_test(lambda x: ti.atan2(0.4, x),
              lambda x: np.arctan2(0.4, x),
              default_fp=ti.f64)
    grad_test(lambda y: ti.atan2(y, 0.4),
              lambda y: np.arctan2(y, 0.4),
              default_fp=ti.f64)
Exemplo n.º 3
0
def computeDstToRefTransform(dst_mean_x, dst_mean_y):
    """
    Compute the rotation matrix and translation vectors to move dst p.c. into ref p.c
    Beased on https://lucidar.me/en/mathematics/singular-value-decomposition-of-a-2x2-matrix/
    """
    a = A[0, 0]
    b = A[1, 0]
    c = A[0, 1]
    d = A[1, 1]

    teta = 0.5 * ti.atan2(2 * a * c + 2 * b * d, a * a + b * b - c * c - d * d)

    U[0, 0] = ti.cos(teta)
    U[1, 0] = -ti.sin(teta)
    U[0, 1] = ti.sin(teta)
    U[1, 1] = ti.cos(teta)

    # We don't need the Sigma matrix for ICP
    # s1 = a*a + b*b + c*c + d*d
    # s2 = ti.sqrt((a*a + b*b - c*c - d*d) * (a*a + b*b - c*c - d*d) + 4 * (a*c + b*d) * (a*c + b*d))
    # sigma1 = ti.sqrt((s1 + s2) * 0.5)
    # sigma2 = ti.sqrt((s1 - s2) * 0.5)
    # S[0,0] = sigma1
    # S[1,0] = 0
    # S[0,1] = 0
    # S[1,1] = sigma2

    phi = 0.5 * ti.atan2(2 * a * b + 2 * c * d, a * a - b * b + c * c - d * d)
    s11 = (a * ti.cos(teta) + c * ti.sin(teta)) * ti.cos(phi) + (
        b * ti.cos(teta) + d * ti.sin(teta)) * ti.sin(phi)
    if s11 != 0:
        s11 = s11 / ti.abs(s11)
    s22 = (a * ti.sin(teta) - c * ti.cos(teta)) * ti.sin(phi) + (
        -b * ti.sin(teta) + d * ti.cos(teta)) * ti.cos(phi)
    if s22 != 0:
        s22 = s22 / ti.abs(s22)
    V[0, 0] = s11 * ti.cos(phi)
    V[1, 0] = -s22 * ti.sin(phi)
    V[0, 1] = s11 * ti.sin(phi)
    V[1, 1] = s22 * ti.cos(phi)

    Rot[0, 0] = V[0, 0] * U[0, 0] + V[1, 0] * U[1, 0]
    Rot[1, 0] = V[0, 0] * U[0, 1] + V[1, 0] * U[1, 1]
    Rot[0, 1] = V[0, 1] * U[0, 0] + V[1, 1] * U[1, 0]
    Rot[1, 1] = V[0, 1] * U[0, 1] + V[1, 1] * U[1, 1]

    Trans[0] = ref_mean[0] - Rot[0, 0] * dst_mean_x - Rot[1, 0] * dst_mean_y
    Trans[1] = ref_mean[1] - Rot[0, 1] * dst_mean_x - Rot[1, 1] * dst_mean_y
Exemplo n.º 4
0
def atan(y, x=1):
    '''
    Return the arc-tangent of the parameters

    `atan(y, x)` or `atan(y_over_x)`:

    `atan` returns the angle whose trigonometric arctangent is y / x or
    y_over_x, depending on which overload is invoked.

    In the first overload, the signs of y and x are used to determine
    the quadrant that the angle lies in. The values returned by atan in
    this case are in the range [−pi, pi]. Results are undefined if x
    is zero.

    For the second overload, atan returns the angle whose tangent
    is y_over_x. Values returned in this case are in the range
    [−pi/2, pi/2].

    :parameter y:
        The numerator of the fraction whose arctangent to return.

    :parameter x:
        The denominator of the fraction whose arctangent to return.

    :return:
        The return value is `arctan(x / y)`.
    '''
    return ti.atan2(y, x)
Exemplo n.º 5
0
def paint(t:ti.f32) :

    for i, j in pixels:
        p = ti.Vector([2*i-Width, 2*j-Height])/min(Width,Height)

        #background-color
        bcol = ti.Vector([1.0,0.8,0.7-0.07*p[1]]) * (1.0-0.25*p.norm())

        #animate
        tt = mod(t, 1.5)/1.5
        ss = pow(tt, 0.2)*0.5+0.5
        ss = 1.0 + ss*0.5*ti.sin(tt*6.2831*3.0+p[1]*0.5)*ti.exp(-4.0*tt)
        p *= ti.Vector([0.5, 1.5]) + ss*ti.Vector([0.5, -0.5])  

        #shape
        p[1] -= 0.25
        a = ti.atan2(p[0], p[1]) / 3.141593
        r = p.norm()
        h = abs(a)
        d = (13.0*h - 22.0*h*h + 10.0*h*h*h)/(6.0-5.0*h)

        #color
        s = 0.75 + 0.75*p[0]
        s *= 1.0 - 0.4*r
        s = 0.3 + 0.7*s
        s *= 0.5 + 0.5*pow(1.0-clamp(r/d, 0.0, 1.0), 1.0)

        hcol = ti.Vector([1.0, 0.5*r, 0.3])*s
        pixels[i,j] = mix(bcol , hcol ,smoothstep(-0.01, 0.01, d-r))
Exemplo n.º 6
0
def skewsin(x, t):
    """
    skewsin-функция
    :param x: радианы
    :param t: значение
    :return: значение skewsin-функции
    """
    return ti.atan2(t * ti.sin(x), (1. - t * ti.cos(x))) / t
Exemplo n.º 7
0
def getBoundaryValue(x_index, y_index):
    dx =  sdf[x_index+1, y_index-1][0] - sdf[x_index-1, y_index-1][0] \
        + sdf[x_index+1, y_index][0]   - sdf[x_index-1, y_index][0]   \
        + sdf[x_index+1, y_index+1][0] - sdf[x_index-1, y_index+1][0]
    dy =  sdf[x_index-1, y_index+1][0] - sdf[x_index-1, y_index-1][0] \
        + sdf[x_index,   y_index+1][0] - sdf[x_index,   y_index-1][0] \
        + sdf[x_index+1, y_index+1][0] - sdf[x_index+1, y_index-1][0]
    return ti.abs(ti.atan2(dy, dx) / 3.14159)
Exemplo n.º 8
0
def _ΔΦ(i: ti.i32, z: ti.i32) -> ti.f32:
    s = 0.0
    t = 0.0
    for r in range(Rf, Nr):
        Δ = (ti.atan2(_J[i, r], _I[i, r]) - _Φ[i, z, r]) % (2 * π)
        if (Δ > π):
            Δ -= 2 * π
        w = ti.sqrt(_I[i, r]**2 + _J[i, r]**2) * _A[i, z, r]
        t += w
        s += w * Δ
    return s / t
Exemplo n.º 9
0
 def random_point_in_unit_polygon(self, sides, angle):
     point = ti.Vector.zero(ti.f32, 2)
     central_angle = 2 * math.pi / sides
     while True:
         point = ti.Vector([ti.random(), ti.random()]) * 2 - 1
         point_angle = ti.atan2(point.y, point.x)
         theta = (point_angle -
                  angle) % central_angle  # polygon angle is from +X axis
         phi = central_angle / 2
         dist = ti.sqrt((point**2).sum())
         if dist < ti.cos(phi) / ti.cos(phi - theta):
             break
     return point
Exemplo n.º 10
0
def sampleEquiAngular(
        u: ti.f32,
        maxDistance: ti.f32,
        rayOrigin: ti.Vector,  # vec3
        rayDir: ti.Vector,  # vec3
        lightPos: ti.Vector  # vec3
):
    # get coord of closest point to light along(infinite) ray
    delta = dot(lightPos - rayOrigin, rayDir)

    # get distance this point is from light
    D = length(rayOrigin + delta * rayDir - lightPos)

    # get angle of endpoints
    thetaA = ti.atan2(0.0 - delta, D)
    thetaB = ti.atan2(maxDistance - delta, D)

    # take sample
    t = D * ti.tan(mix(thetaA, thetaB, u))
    dist = delta + t
    pdf = D / ((thetaB - thetaA) * (D * D + t * t))
    return (dist, pdf)
Exemplo n.º 11
0
def getDist(p):
    diskPos = -1.0 * p
    diskDist = sphere(ti.Vector([diskPos[0], diskPos[1], diskPos[2], 5.0]))
    diskDist = max(diskDist, diskPos[1] - 0.01)
    diskDist = max(diskDist, -1.0 * diskPos[1] - 0.01)
    diskDist = max(
        diskDist, -1.0 *
        sphere(ti.Vector([-1.0 * p[0], -1.0 * p[1], -1.0 * p[2], 1.5]) * 10.0))
    if diskDist < 2.0:
        c = ti.Vector([
            diskPos.norm(), diskPos[1],
            ti.atan2(diskPos[2] + 1.0, diskPos[0] + 1.0) * 0.5
        ])
        c *= 10.0
        diskDist += noise(c) * 0.4
        diskDist += noise(c * 2.5) * 0.2
    return diskDist
Exemplo n.º 12
0
def friction(t: ti.i32):
    # params V, R
    # updates Fupdated

    for prtcl in range(N):
        vx, vy = V[t, prtcl]
        v = ti.sqrt(vx * vx + vy * vy)
        theta = ti.atan2(vy, vx)

        # random doesnt seem to work in tape scope
        ffx = friction_coef[None] * v * ti.cos(
            theta)  # + ti.randn() * temperature
        ffy = friction_coef[None] * v * ti.sin(
            theta)  # + ti.randn() * temperature

        Fupdated[t, prtcl][0] += ffx
        Fupdated[t, prtcl][0] += ffx
Exemplo n.º 13
0
 def func():
     x[0] = y[None] + z[None]
     x[1] = y[None] - z[None]
     x[2] = y[None] * z[None]
     x[3] = y[None] / z[None]
     x[4] = y[None] // z[None]
     x[5] = y[None] % z[None]
     x[6] = y[None]**z[None]
     x[7] = y[None] == z[None]
     x[8] = y[None] != z[None]
     x[9] = y[None] > z[None]
     x[10] = y[None] >= z[None]
     x[11] = y[None] < z[None]
     x[12] = y[None] <= z[None]
     x[13] = ti.atan2(y[None], z[None])
     x[14] = ti.min(y[None], z[None])
     x[15] = ti.max(y[None], z[None])
Exemplo n.º 14
0
def test_constant_matrices():
    assert ti.cos(ti.math.pi / 3) == approx(0.5)
    assert np.allclose((-ti.Vector([2, 3])).to_numpy(), np.array([-2, -3]))
    assert ti.cos(ti.Vector([2,
                             3])).to_numpy() == approx(np.cos(np.array([2,
                                                                        3])))
    assert ti.max(2, 3) == 3
    res = ti.max(4, ti.Vector([3, 4, 5]))
    assert np.allclose(res.to_numpy(), np.array([4, 4, 5]))
    res = ti.Vector([2, 3]) + ti.Vector([3, 4])
    assert np.allclose(res.to_numpy(), np.array([5, 7]))
    res = ti.atan2(ti.Vector([2, 3]), ti.Vector([3, 4]))
    assert res.to_numpy() == approx(
        np.arctan2(np.array([2, 3]), np.array([3, 4])))
    res = ti.Matrix([[2, 3], [4, 5]]) @ ti.Vector([2, 3])
    assert np.allclose(res.to_numpy(), np.array([13, 23]))
    v = ti.Vector([3, 4])
    w = ti.Vector([5, -12])
    r = ti.Vector([1, 2, 3, 4])
    s = ti.Matrix([[1, 2], [3, 4]])
    assert v.normalized().to_numpy() == approx(np.array([0.6, 0.8]))
    assert v.cross(w) == approx(-12 * 3 - 4 * 5)
    w.y = v.x * w[0]
    r.x = r.y
    r.y = r.z
    r.z = r.w
    r.w = r.x
    assert np.allclose(w.to_numpy(), np.array([5, 15]))
    assert ti.select(ti.Vector([1, 0]), ti.Vector([2, 3]),
                     ti.Vector([4, 5])) == ti.Vector([2, 5])
    s[0, 1] = 2
    assert s[0, 1] == 2

    @ti.kernel
    def func(t: ti.i32):
        m = ti.Matrix([[2, 3], [4, t]])
        print(m @ ti.Vector([2, 3]))
        m += ti.Matrix([[3, 4], [5, t]])
        print(m @ v)
        print(r.x, r.y, r.z, r.w)
        s = w.transpose() @ m
        print(s)
        print(m)

    func(5)
Exemplo n.º 15
0
 def ang(self):
     '''Phase angle of the complex'''
     return ti.atan2(self.y, self.x)
Exemplo n.º 16
0
 def test_case_2() -> ti.f32:
     x[0] = ti.i32(3)
     y[0] = ti.i32(1)
     return ti.atan2(x[0], y[0])
Exemplo n.º 17
0
 def test_case_1() -> ti.f32:
     x[0] = ti.i32(2)
     return ti.atan2(x[0], 1)
Exemplo n.º 18
0
 def test_case_0() -> ti.f32:
     i = ti.i32(2)
     return ti.atan2(i, 1)
Exemplo n.º 19
0
    def func():
        y[0] = x[0] % 3

    @ti.kernel
    def func2():
        ti.atomic_add(y[0], x[0] % 3)

    func()
    func.grad()

    func2()
    func2.grad()


@pytest.mark.parametrize('tifunc,npfunc', [
    (lambda x: ti.atan2(0.4, x), lambda x: np.arctan2(0.4, x)),
    (lambda y: ti.atan2(y, 0.4), lambda y: np.arctan2(y, 0.4)),
])
@if_has_autograd
@test_utils.test()
def test_atan2(tifunc, npfunc):
    grad_test(tifunc, npfunc)


@pytest.mark.parametrize('tifunc,npfunc', [
    (lambda x: ti.atan2(0.4, x), lambda x: np.arctan2(0.4, x)),
    (lambda y: ti.atan2(y, 0.4), lambda y: np.arctan2(y, 0.4)),
])
@if_has_autograd
@test_utils.test(require=ti.extension.data64, default_fp=ti.f64)
def test_atan2_f64(tifunc, npfunc):
Exemplo n.º 20
0
def increment_vector_inplace(
        array_vector: ti.template(), magnitude: float, dy: float, dx: float):
    # increment array_vector (which is/may be row in an [? x 2]) vector field)
    theta = ti.atan2(dy, dx)
    array_vector[0] += magnitude * ti.cos(theta)
    array_vector[1] += magnitude * ti.sin(theta)
Exemplo n.º 21
0
def computeNormalAndCurvature():
    """
    Compute the normal and the curvature at all points voxels.
    Based on the PC limplementation:
    https://pointclouds.org/documentation/group__features.html
    """
    radius = 50
    for i,j in pts:
        nb_pts = ti.cast(0, ti.f32)
        accu_0 = ti.cast(0, ti.f32)
        accu_1 = ti.cast(0, ti.f32)
        accu_2 = ti.cast(0, ti.f32)
        accu_3 = ti.cast(0, ti.f32)
        accu_4 = ti.cast(0, ti.f32)
        accu_5 = ti.cast(0, ti.f32)
        accu_6 = ti.cast(0, ti.f32)
        accu_7 = ti.cast(0, ti.f32)
        accu_8 = ti.cast(0, ti.f32)
        z = 0
        for x in range(i-radius, i+radius):
            for y in range(j-radius, j+radius):
                if ti.is_active(block1, [x,y]):
                    accu_0 += x * x
                    accu_1 += x * y
                    accu_2 += x * z
                    accu_3 += y * y
                    accu_4 += y * z
                    accu_5 += z * z
                    accu_6 += x
                    accu_7 += y
                    accu_8 += z
                    nb_pts += 1
        accu_0 /= nb_pts
        accu_1 /= nb_pts
        accu_2 /= nb_pts
        accu_3 /= nb_pts
        accu_4 /= nb_pts
        accu_5 /= nb_pts
        accu_6 /= nb_pts
        accu_7 /= nb_pts
        accu_8 /= nb_pts
        cov_mat_0 = accu_0 - accu_6 * accu_6
        cov_mat_1 = accu_1 - accu_6 * accu_7
        cov_mat_2 = accu_2 - accu_6 * accu_8
        cov_mat_4 = accu_3 - accu_7 * accu_7
        cov_mat_5 = accu_4 - accu_7 * accu_8
        cov_mat_8 = accu_5 - accu_8 * accu_8
        cov_mat_3 = cov_mat_1
        cov_mat_6 = cov_mat_2
        cov_mat_7 = cov_mat_5

        # Compute eigen value and eigen vector
        # Make sure in [-1, 1]
        scale = ti.max(1.0,   ti.abs(cov_mat_0))
        scale = ti.max(scale, ti.abs(cov_mat_1))
        scale = ti.max(scale, ti.abs(cov_mat_2))
        scale = ti.max(scale, ti.abs(cov_mat_3))
        scale = ti.max(scale, ti.abs(cov_mat_4))
        scale = ti.max(scale, ti.abs(cov_mat_5))
        scale = ti.max(scale, ti.abs(cov_mat_6))
        scale = ti.max(scale, ti.abs(cov_mat_7))
        scale = ti.max(scale, ti.abs(cov_mat_8))
        if scale > 1.0:
            cov_mat_0 /= scale
            cov_mat_1 /= scale
            cov_mat_2 /= scale
            cov_mat_3 /= scale
            cov_mat_4 /= scale
            cov_mat_5 /= scale
            cov_mat_6 /= scale
            cov_mat_7 /= scale
            cov_mat_8 /= scale
        
        # Compute roots
        eigen_val_0 = ti.cast(0, ti.f32)
        eigen_val_1 = ti.cast(0, ti.f32)
        eigen_val_2 = ti.cast(0, ti.f32)
        
        c0 = cov_mat_0 * cov_mat_4 * cov_mat_8 \
            + 2 * cov_mat_3 * cov_mat_6 * cov_mat_7 \
            - cov_mat_0 * cov_mat_7 * cov_mat_7 \
            - cov_mat_4 * cov_mat_6 * cov_mat_6 \
            - cov_mat_8 * cov_mat_3 * cov_mat_3
        c1 = cov_mat_0 * cov_mat_4 \
            - cov_mat_3 * cov_mat_3 \
            + cov_mat_0 * cov_mat_8 \
            - cov_mat_6 * cov_mat_6 \
            + cov_mat_4 * cov_mat_8 \
            - cov_mat_7 * cov_mat_7
        c2 = cov_mat_0 + cov_mat_4 + cov_mat_8
  
        if ti.abs(c0) < 0.00001:
            eigen_val_0 = 0
            d = c2 * c2 - 4.0 * c1
            if d < 0.0:  # no real roots ! THIS SHOULD NOT HAPPEN!
                d = 0.0
            sd = ti.sqrt(d)
            eigen_val_2 = 0.5 * (c2 + sd)
            eigen_val_1 = 0.5 * (c2 - sd)
        else:
            s_inv3 = ti.cast(1.0 / 3.0, ti.f32)
            s_sqrt3 = ti.sqrt(3.0)
            c2_over_3 = c2 * s_inv3
            a_over_3 = (c1 - c2 * c2_over_3) * s_inv3
            if a_over_3 > 0:
                a_over_3 = 0
        
            half_b = 0.5 * (c0 + c2_over_3 * (2 * c2_over_3 * c2_over_3 - c1))
            q = half_b * half_b + a_over_3 * a_over_3 * a_over_3
            if q > 0:
                q = 0
        
            rho = ti.sqrt(-a_over_3)
            theta = ti.atan2(ti.sqrt(-q), half_b) * s_inv3
            cos_theta = ti.cos(theta)
            sin_theta = ti.sin(theta)
            eigen_val_0 = c2_over_3 + 2 * rho * cos_theta
            eigen_val_1 = c2_over_3 - rho * (cos_theta + s_sqrt3 * sin_theta)
            eigen_val_2 = c2_over_3 - rho * (cos_theta - s_sqrt3 * sin_theta)
            temp_swap = ti.cast(0, ti.f32)
        
            # Sort in increasing order.
            if eigen_val_0 >= eigen_val_1:
                temp_swap = eigen_val_1
                eigen_val_1 = eigen_val_0
                eigen_val_0 = temp_swap
            if eigen_val_1 >= eigen_val_2:
                temp_swap = eigen_val_2
                eigen_val_2 = eigen_val_1
                eigen_val_1 = temp_swap
                if eigen_val_0 >= eigen_val_1:
                    temp_swap = eigen_val_1
                    eigen_val_1 = eigen_val_0
                    eigen_val_0 = temp_swap
        
            if eigen_val_0 <= 0:
                eigen_val_0 = 0
                d = c2 * c2 - 4.0 * c1
                if d < 0.0:  # no real roots ! THIS SHOULD NOT HAPPEN!
                    d = 0.0
                sd = ti.sqrt(d)
                eigen_val_2 = 0.5 * (c2 + sd)
                eigen_val_1 = 0.5 * (c2 - sd)
            # end of compute roots

        eigen_value = eigen_val_1 * scale # eigen value for 2D SDF
        # eigen value for 3D SDF
        #eigen_value = eigen_val_0 * scale

        #print("eigen_val_0 ", eigen_val_0)
        #print("eigen_val_1 ", eigen_val_1)
        #print("eigen_val_2 ", eigen_val_2)
    
        # TODO
        #scaledMat.diagonal ().array () -= eigenvalues (0)
        #eigenvector = detail::getLargest3x3Eigenvector<Vector> (scaledMat).vector;

        # Compute normal vector (TODO)
        #visual_norm[i,j][0] = eigen_val_0 #eigen_vector[0]
        #visual_norm[i,j][1] = eigen_val_1 #eigen_vector[1]
        #visual_norm[i,j][2] = eigen_val_2 #eigen_vector[2]

        # Compute the curvature surface change
        eig_sum = cov_mat_0 + cov_mat_1 + cov_mat_2
        visual_curv[i,j][0] = 0
        if eig_sum != 0:
            visual_curv[i,j][0] = eigen_val_1 # true curvature is: ti.abs(eigen_value / eig_sum)
Exemplo n.º 22
0
def unspherical(dir):
    p = ti.atan2(dir.y, dir.x) / ti.tau
    return dir.z, p % 1
Exemplo n.º 23
0
def complex_power(z, power: ti.i32):
    r = ti.sqrt(z[0]**2 + z[1]**2)
    theta = ti.atan2(z[1], z[0])
    return ti.Vector(
        [r**power * ti.cos(power * theta), r**power * ti.sin(power * theta)])
Exemplo n.º 24
0
def dir2tex(dir):
    dir = dir.normalized()
    s = ti.atan2(dir.z, dir.x) / ti.pi * 0.5 + 0.5
    t = ti.atan2(dir.y, dir.xz.norm()) / ti.pi + 0.5
    return V(s, t)
Exemplo n.º 25
0
 def foo():
     y[None] = x[None]**2
     z[None] = ti.atan2(y[None], 0.3)
Exemplo n.º 26
0
def test_atan2():
  grad_test(lambda x: ti.atan2(0.4, x), lambda x: np.arctan2(0.4, x))
  grad_test(lambda y: ti.atan2(y, 0.4), lambda y: np.arctan2(y, 0.4))
Exemplo n.º 27
0
def test_atan2_f64():
    ti.set_default_fp(ti.f64)
    grad_test(lambda x: ti.atan2(0.4, x), lambda x: np.arctan2(0.4, x))
    grad_test(lambda y: ti.atan2(y, 0.4), lambda y: np.arctan2(y, 0.4))