Ejemplo n.º 1
0
def Wigner3jCached(j_1,j_2,j_3,m_1,m_2,m_3):
    """Wrapper for 3j caching with functools.lru_cache"""
    return sf.Wigner3j(j_1,j_2,j_3,m_1,m_2,m_3)
Ejemplo n.º 2
0
def single_mode_proportional_to_time_supertranslated(**kwargs):
    """Return WaveformModes as in single_mode_proportional_to_time, with analytical supertranslation

    This function constructs the same basic object as the `single_mode_proportional_to_time`, but then applies an
    analytical supertranslation.  The arguments to this function are the same as to the other, with two additions:

    Additional parameters
    ---------------------
    supertranslation : complex array, optional
        Spherical-harmonic modes of the supertranslation to apply to the waveform.  This is overwritten by
         `space_translation` if present.  Default value is `None`.
    space_translation : float array of length 3, optional
        This is just the 3-vector representing the displacement to apply to the waveform.  Note that if
        `supertranslation`, this parameter overwrites it.  Default value is [1.0, 0.0, 0.0].

    """
    s = kwargs.pop("s", -2)
    ell = kwargs.pop("ell", abs(s))
    m = kwargs.pop("m", -ell)
    ell_min = kwargs.pop("ell_min", abs(s))
    ell_max = kwargs.pop("ell_max", 8)
    data_type = kwargs.pop("data_type",
                           scri.DataType[scri.SpinWeights.index(s)])
    t_0 = kwargs.pop("t_0", -20.0)
    t_1 = kwargs.pop("t_1", 20.0)
    dt = kwargs.pop("dt", 1.0 / 10.0)
    t = np.arange(t_0, t_1 + dt, dt)
    n_times = t.size
    beta = kwargs.pop("beta", 1.0)
    data = np.zeros((n_times, sf.LM_total_size(ell_min, ell_max)),
                    dtype=complex)
    data[:, sf.LM_index(ell, m, ell_min)] = beta * t
    supertranslation = np.array(kwargs.pop("supertranslation",
                                           np.array([], dtype=complex)),
                                dtype=complex)
    if "space_translation" in kwargs:
        if supertranslation.size < 4:
            supertranslation.resize((4, ))
        supertranslation[1:4] = -sf.vector_as_ell_1_modes(
            kwargs.pop("space_translation"))
    supertranslation_ell_max = int(math.sqrt(supertranslation.size) - 1)
    if supertranslation_ell_max * (supertranslation_ell_max +
                                   2) + 1 != supertranslation.size:
        raise ValueError(
            f"Bad number of elements in supertranslation: {supertranslation.size}"
        )
    for i, (ellpp, mpp) in enumerate(sf.LM_range(0, supertranslation_ell_max)):
        if supertranslation[i] != 0.0:
            mp = m + mpp
            for ellp in range(ell_min, min(ell_max, (ell + ellpp)) + 1):
                if ellp >= abs(mp):
                    addition = (beta * supertranslation[i] * math.sqrt(
                        ((2 * ellpp + 1) * (2 * ell + 1) *
                         (2 * ellp + 1)) / (4 * math.pi)) *
                                sf.Wigner3j(ellpp, ell, ellp, 0, -s, s) *
                                sf.Wigner3j(ellpp, ell, ellp, mpp, m, -mp))
                    if (s + mp) % 2 == 1:
                        addition *= -1
                    data[:, sf.LM_index(ellp, mp, ell_min)] += addition

    if kwargs:
        import pprint

        warnings.warn(
            f"\nUnused kwargs passed to this function:\n{pprint.pformat(kwargs, width=1)}"
        )

    return scri.WaveformModes(
        t=t,
        data=data,
        ell_min=ell_min,
        ell_max=ell_max,
        frameType=scri.Inertial,
        dataType=data_type,
        r_is_scaled_out=True,
        m_is_scaled_out=True,
    )
 def specialized_multiplication(f, ellmin_f, ellmax_f, s_f, g, ellmin_g,
                                ellmax_g, s_g):
     ellmin_fg = 0
     ellmax_fg = ellmax_f + ellmax_g
     s_fg = s_f + s_g
     fg = np.zeros(sf.LM_total_size(0, ellmax_fg), dtype=np.complex)
     # for ell in range(ellmin_g, ellmax_g+1):
     #     for m in range(-ell, ell+1):
     #         i_g = sf.LM_index(ell, m, ellmin_g)
     #         for ellprime in [ell-1, ell, ell+1]:
     #             if ellprime < ellmin_g or ellprime > ellmax_g or ellprime < abs(s_g):
     #                 continue
     #             for mprime in [m-1, m, m+1]:
     #                 if mprime < -ellprime or mprime > ellprime:
     #                     continue
     #                 i_fg = sf.LM_index(ellprime, mprime, ellmin_fg)
     #                 m1 = mprime - m
     #                 fg[i_fg] += (
     #                     # (-1)**(s_g+m+1)
     #                     # * np.sqrt(3*(2*ell+1)/(4*np.pi))
     #                     # * (-1)**(m1)
     #                     # * np.sqrt(2*ellprime+1)
     #                     (-1)**(1 + ell + ellprime + s_g + m + m1)
     #                     * np.sqrt(3*(2*ell+1)*(2*ellprime+1)/(4*np.pi))
     #                     * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ellprime, m1, m, -mprime)
     #                     * f[sf.LM_index(1, m1, 0)]
     #                     * g[i_g]
     #                 )
     ####################################################################################
     # for ell in range(ellmin_g, ellmax_g+1):
     #     for m in range(-ell, ell+1):
     #         i_g = sf.LM_index(ell, m, ellmin_g)
     #         coefficient = (-1)**(s_g + m) * np.sqrt(3*(2*ell+1)/(4*np.pi))
     #         for ellprime in [ell-1, ell, ell+1]:
     #             if ellprime < ellmin_g or ellprime > ellmax_g or ellprime < abs(s_g):
     #                 continue
     #             if m-1 >= -ellprime and m-1 <= ellprime:
     #                 i_fg = sf.LM_index(ellprime, m-1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * (-1)**(ell + ellprime)
     #                     * np.sqrt((2*ellprime+1))
     #                     * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ellprime, -1, m, -m+1)
     #                     * f[sf.LM_index(1, -1, 0)]
     #                     * g[i_g]
     #                 )
     #             if m >= -ellprime and m <= ellprime:
     #                 i_fg = sf.LM_index(ellprime, m, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * (-1)**(ell + ellprime)
     #                     * -np.sqrt(2*ellprime+1)
     #                     * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ellprime, 0, m, -m)
     #                     * f[sf.LM_index(1, 0, 0)]
     #                     * g[i_g]
     #                 )
     #             if m+1 >= -ellprime and m+1 <= ellprime:
     #                 i_fg = sf.LM_index(ellprime, m+1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * (-1)**(ell + ellprime)
     #                     * np.sqrt(2*ellprime+1)
     #                     * sf.Wigner3j(1, ell, ellprime, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ellprime, 1, m, -m-1)
     #                     * f[sf.LM_index(1, 1, 0)]
     #                     * g[i_g]
     #                 )
     ####################################################################################
     # for ell in range(ellmin_g, ellmax_g+1):
     #     for m in range(-ell, ell+1):
     #         i_g = sf.LM_index(ell, m, ellmin_g)
     #         coefficient = (-1)**(s_g + m + 1) * np.sqrt(3*(2*ell+1)/(4*np.pi))
     #         if ell-1 >= ellmin_g and ell-1 <= ellmax_g and ell-1 >= abs(s_g):
     #             if m-1 >= -ell+1 and m-1 <= ell-1:
     #                 i_fg = sf.LM_index(ell-1, m-1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * np.sqrt(2*ell-1)
     #                     * sf.Wigner3j(1, ell, ell-1, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell-1, -1, m, -m+1)
     #                     * f[sf.LM_index(1, -1, 0)]
     #                     * g[i_g]
     #                 )
     #             if m >= -ell+1 and m <= ell-1:
     #                 i_fg = sf.LM_index(ell-1, m, ellmin_fg)
     #                 fg[i_fg] += (
     #                     -coefficient
     #                     * np.sqrt(2*ell-1)
     #                     * sf.Wigner3j(1, ell, ell-1, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell-1, 0, m, -m)
     #                     * f[sf.LM_index(1, 0, 0)]
     #                     * g[i_g]
     #                 )
     #             if m+1 >= -ell+1 and m+1 <= ell-1:
     #                 i_fg = sf.LM_index(ell-1, m+1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * np.sqrt(2*ell-1)
     #                     * sf.Wigner3j(1, ell, ell-1, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell-1, 1, m, -m-1)
     #                     * f[sf.LM_index(1, 1, 0)]
     #                     * g[i_g]
     #                 )
     #         if ell >= ellmin_g and ell <= ellmax_g and ell >= abs(s_g):
     #             if m-1 >= -ell and m-1 <= ell:
     #                 i_fg = sf.LM_index(ell, m-1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     -coefficient
     #                     * np.sqrt(2*ell+1)
     #                     * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell, -1, m, -m+1)
     #                     * f[sf.LM_index(1, -1, 0)]
     #                     * g[i_g]
     #                 )
     #             if m >= -ell and m <= ell:
     #                 i_fg = sf.LM_index(ell, m, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * np.sqrt(2*ell+1)
     #                     * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell, 0, m, -m)
     #                     * f[sf.LM_index(1, 0, 0)]
     #                     * g[i_g]
     #                 )
     #             if m+1 >= -ell and m+1 <= ell:
     #                 i_fg = sf.LM_index(ell, m+1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     -coefficient
     #                     * np.sqrt(2*ell+1)
     #                     * sf.Wigner3j(1, ell, ell, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell, 1, m, -m-1)
     #                     * f[sf.LM_index(1, 1, 0)]
     #                     * g[i_g]
     #                 )
     #         if ell+1 >= ellmin_g and ell+1 <= ellmax_g and ell+1 >= abs(s_g):
     #             if m-1 >= -ell-1 and m-1 <= ell+1:
     #                 i_fg = sf.LM_index(ell+1, m-1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * np.sqrt(2*ell+3)
     #                     * sf.Wigner3j(1, ell, ell+1, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell+1, -1, m, -m+1)
     #                     * f[sf.LM_index(1, -1, 0)]
     #                     * g[i_g]
     #                 )
     #             if m >= -ell-1 and m <= ell+1:
     #                 i_fg = sf.LM_index(ell+1, m, ellmin_fg)
     #                 fg[i_fg] += (
     #                     -coefficient
     #                     * np.sqrt(2*ell+3)
     #                     * sf.Wigner3j(1, ell, ell+1, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell+1, 0, m, -m)
     #                     * f[sf.LM_index(1, 0, 0)]
     #                     * g[i_g]
     #                 )
     #             if m+1 >= -ell-1 and m+1 <= ell+1:
     #                 i_fg = sf.LM_index(ell+1, m+1, ellmin_fg)
     #                 fg[i_fg] += (
     #                     coefficient
     #                     * np.sqrt(2*ell+3)
     #                     * sf.Wigner3j(1, ell, ell+1, 0, s_g, -s_g)
     #                     * sf.Wigner3j(1, ell, ell+1, 1, m, -m-1)
     #                     * f[sf.LM_index(1, 1, 0)]
     #                     * g[i_g]
     #                 )
     ####################################################################################
     for ell in range(ellmin_g, ellmax_g + 1):
         for m in range(-ell, ell + 1):
             i_fg = sf.LM_index(ell, m, ellmin_fg)
             coefficient = (-1)**(s_g + m) * np.sqrt(3 * (2 * ell + 1) /
                                                     (4 * np.pi))
             if ell + 1 >= ellmin_g and ell + 1 <= ellmax_g and ell + 1 >= abs(
                     s_g):
                 if m + 1 >= -ell - 1 and m + 1 <= ell + 1:
                     fg[i_fg] += (
                         coefficient * np.sqrt(2 * ell + 3) *
                         sf.Wigner3j(1, ell + 1, ell, 0, s_g, -s_g) *
                         sf.Wigner3j(1, ell + 1, ell, -1, m + 1, -m) *
                         f[sf.LM_index(1, -1, 0)] *
                         g[sf.LM_index(ell + 1, m + 1, ellmin_g)])
                 if m >= -ell - 1 and m <= ell + 1:
                     fg[i_fg] += (
                         coefficient * np.sqrt(2 * ell + 3) *
                         sf.Wigner3j(1, ell + 1, ell, 0, s_g, -s_g) *
                         sf.Wigner3j(1, ell + 1, ell, 0, m, -m) *
                         f[sf.LM_index(1, 0, 0)] *
                         g[sf.LM_index(ell + 1, m, ellmin_g)])
                 if m - 1 >= -ell - 1 and m - 1 <= ell + 1:
                     fg[i_fg] += (
                         coefficient * np.sqrt(2 * ell + 3) *
                         sf.Wigner3j(1, ell + 1, ell, 0, s_g, -s_g) *
                         sf.Wigner3j(1, ell + 1, ell, 1, m - 1, -m) *
                         f[sf.LM_index(1, 1, 0)] *
                         g[sf.LM_index(ell + 1, m - 1, ellmin_g)])
             if ell >= ellmin_g and ell <= ellmax_g and ell >= abs(s_g):
                 if m + 1 >= -ell and m + 1 <= ell:
                     fg[i_fg] += (-coefficient * np.sqrt(2 * ell + 1) *
                                  sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) *
                                  sf.Wigner3j(1, ell, ell, -1, m + 1, -m) *
                                  f[sf.LM_index(1, -1, 0)] *
                                  g[sf.LM_index(ell, m + 1, ellmin_g)])
                 if m >= -ell and m <= ell:
                     fg[i_fg] += (-coefficient * np.sqrt(2 * ell + 1) *
                                  sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) *
                                  sf.Wigner3j(1, ell, ell, 0, m, -m) *
                                  f[sf.LM_index(1, 0, 0)] *
                                  g[sf.LM_index(ell, m, ellmin_g)])
                 if m - 1 >= -ell and m - 1 <= ell:
                     fg[i_fg] += (-coefficient * np.sqrt(2 * ell + 1) *
                                  sf.Wigner3j(1, ell, ell, 0, s_g, -s_g) *
                                  sf.Wigner3j(1, ell, ell, 1, m - 1, -m) *
                                  f[sf.LM_index(1, 1, 0)] *
                                  g[sf.LM_index(ell, m - 1, ellmin_g)])
             if ell - 1 >= ellmin_g and ell - 1 <= ellmax_g and ell - 1 >= abs(
                     s_g):
                 if m + 1 >= -ell + 1 and m + 1 <= ell - 1:
                     fg[i_fg] += (
                         coefficient * np.sqrt(2 * ell - 1) *
                         sf.Wigner3j(1, ell - 1, ell, 0, s_g, -s_g) *
                         sf.Wigner3j(1, ell - 1, ell, -1, m + 1, -m) *
                         f[sf.LM_index(1, -1, 0)] *
                         g[sf.LM_index(ell - 1, m + 1, ellmin_g)])
                 if m >= -ell + 1 and m <= ell - 1:
                     fg[i_fg] += (
                         coefficient * np.sqrt(2 * ell - 1) *
                         sf.Wigner3j(1, ell - 1, ell, 0, s_g, -s_g) *
                         sf.Wigner3j(1, ell - 1, ell, 0, m, -m) *
                         f[sf.LM_index(1, 0, 0)] *
                         g[sf.LM_index(ell - 1, m, ellmin_g)])
                 if m - 1 >= -ell + 1 and m - 1 <= ell - 1:
                     fg[i_fg] += (
                         coefficient * np.sqrt(2 * ell - 1) *
                         sf.Wigner3j(1, ell - 1, ell, 0, s_g, -s_g) *
                         sf.Wigner3j(1, ell - 1, ell, 1, m - 1, -m) *
                         f[sf.LM_index(1, 1, 0)] *
                         g[sf.LM_index(ell - 1, m - 1, ellmin_g)])
     return fg, ellmin_fg, ellmax_fg, s_fg
def test_SWSH_multiplication_formula(multiplication_function):
    """Test formula for multiplication of SWSHs

    Much of the analysis is based on the formula

    s1Yl1m1 * s2Yl2m2 = sum([
        s3Yl3m3 * (-1)**(l1+l2+l3+s3+m3) * sqrt((2*l1+1)*(2*l2+1)*(2*l3+1)/(4*pi))
            * Wigner3j(l1, l2, l3, s1, s2, -s3) * Wigner3j(l1, l2, l3, m1, m2, -m3)
        for s3 in [s1+s2]
        for l3 in range(abs(l1-l2), l1+l2+1)
        for m3 in [m1+m2]
    ])

    This test evaluates each side of this formula, and compares the values at all collocation points
    on the sphere.  This is tested for each possible value of (s1, l1, m1, s2, l2, m2) up to l1=4
    and l2=4 [the number of items to test scales very rapidly with ell], and tested again for each
    (0, 1, m1, s2, l2, m2) up to l2=8.

    """
    atol = 2e-15
    rtol = 2e-15
    ell_max = 4
    for ell1 in range(ell_max + 1):
        for s1 in range(-ell1, ell1 + 1):
            for m1 in range(-ell1, ell1 + 1):
                for ell2 in range(ell_max + 1):
                    for s2 in range(-ell2, ell2 + 1):
                        for m2 in range(-ell2, ell2 + 1):
                            swsh1 = np.zeros(sf.LM_total_size(0, ell_max),
                                             dtype=np.complex)
                            swsh1[sf.LM_index(ell1, m1, 0)] = 1.0
                            swsh2 = np.zeros(sf.LM_total_size(0, ell_max),
                                             dtype=np.complex)
                            swsh2[sf.LM_index(ell2, m2, 0)] = 1.0
                            swsh3, ell_min_3, ell_max_3, s3 = multiplication_function(
                                swsh1, 0, ell_max, s1, swsh2, 0, ell_max, s2)
                            assert s3 == s1 + s2
                            assert ell_min_3 == 0
                            assert ell_max_3 == 2 * ell_max
                            n_theta = 2 * ell_max_3 + 1
                            n_phi = n_theta
                            swsh3_map = spinsfast.salm2map(
                                swsh3, s3, ell_max_3, n_theta, n_phi)
                            swsh4_map = np.zeros_like(swsh3_map)
                            for ell4 in range(abs(ell1 - ell2),
                                              ell1 + ell2 + 1):
                                for s4 in [s1 + s2]:
                                    for m4 in [m1 + m2]:
                                        swsh4_k = np.zeros_like(swsh3)
                                        swsh4_k[sf.LM_index(ell4, m4, 0)] = (
                                            (-1)
                                            **(ell1 + ell2 + ell4 + s4 + m4) *
                                            np.sqrt(
                                                (2 * ell1 + 1) *
                                                (2 * ell2 + 1) *
                                                (2 * ell4 + 1) / (4 * np.pi)) *
                                            sf.Wigner3j(
                                                ell1, ell2, ell4, s1, s2, -s4)
                                            * sf.Wigner3j(
                                                ell1, ell2, ell4, m1, m2, -m4))
                                        swsh4_map[:] += spinsfast.salm2map(
                                            swsh4_k, s4, ell_max_3, n_theta,
                                            n_phi)
                            assert np.allclose(swsh3_map,
                                               swsh4_map,
                                               atol=atol,
                                               rtol=rtol)

    atol = 8e-15
    rtol = 8e-15
    ell_max = 8
    for ell1 in [1]:
        for s1 in [0]:
            for m1 in [-1, 0, 1]:
                for ell2 in range(ell_max + 1):
                    for s2 in range(-ell2, ell2 + 1):
                        for m2 in range(-ell2, ell2 + 1):
                            swsh1 = np.zeros(sf.LM_total_size(0, ell_max),
                                             dtype=np.complex)
                            swsh1[sf.LM_index(ell1, m1, 0)] = 1.0
                            swsh2 = np.zeros(sf.LM_total_size(0, ell_max),
                                             dtype=np.complex)
                            swsh2[sf.LM_index(ell2, m2, 0)] = 1.0
                            swsh3, ell_min_3, ell_max_3, s3 = multiplication_function(
                                swsh1, 0, ell_max, s1, swsh2, 0, ell_max, s2)
                            assert s3 == s1 + s2
                            assert ell_min_3 == 0
                            assert ell_max_3 == 2 * ell_max
                            n_theta = 2 * ell_max_3 + 1
                            n_phi = n_theta
                            swsh3_map = spinsfast.salm2map(
                                swsh3, s3, ell_max_3, n_theta, n_phi)
                            swsh4_map = np.zeros_like(swsh3_map)
                            for ell4 in [ell2 - 1, ell2, ell2 + 1]:
                                if ell4 < 0:
                                    continue
                                swsh4_k = np.zeros_like(swsh3)
                                # swsh4_k[sf.LM_index(ell4, m1+m2, 0)] = (
                                #     (-1)**(1+ell2+ell4+s2+m1+m2)
                                #     * np.sqrt(3*(2*ell2+1)*(2*ell4+1)/(4*np.pi))
                                #     * sf.Wigner3j(1, ell2, ell4, 0, s2, -s2)
                                #     * sf.Wigner3j(1, ell2, ell4, m1, m2, -m1-m2)
                                # )
                                # swsh4_map[:] += (
                                #     spinsfast.salm2map(swsh4_k, s2, ell_max_3, n_theta, n_phi)
                                # )
                                swsh4_k[sf.LM_index(ell4, m1 + m2, 0)] = (
                                    (-1)**(ell2 + ell4 + m1) * np.sqrt(
                                        (2 * ell4 + 1)) *
                                    sf.Wigner3j(1, ell2, ell4, 0, s2, -s2) *
                                    sf.Wigner3j(1, ell2, ell4, m1, m2,
                                                -m1 - m2))
                                swsh4_map[:] += (
                                    (-1)**(s2 + m2 + 1) *
                                    np.sqrt(3 * (2 * ell2 + 1) / (4 * np.pi)) *
                                    spinsfast.salm2map(swsh4_k, s2, ell_max_3,
                                                       n_theta, n_phi))
                            assert np.allclose(swsh3_map,
                                               swsh4_map,
                                               atol=atol,
                                               rtol=rtol), np.max(
                                                   np.abs(swsh3_map -
                                                          swsh4_map))
Ejemplo n.º 5
0
def test_Wigner3j_properties():
    assert abs(sf.Wigner3j(2, 6, 4, 0, 0, 0) -
               0.1869893980016914) < precision_Wigner3j
    ## The following test various symmetries and other properties fo
    ## the Wigner 3-j symbols
    j_max = 8
    for j1 in range(j_max + 1):
        for j2 in range(j_max + 1):
            for j3 in range(j_max + 1):
                # Selection rule
                if ((j1 + j2 + j3) % 2 != 0):
                    assert abs(sf.Wigner3j(j1, j2, j3, 0, 0,
                                           0)) < precision_Wigner3j
                for m1 in range(-j1, j1 + 1):
                    for m2 in range(-j2, j2 + 1):
                        # Selection rule
                        if abs(j1 - j2) > j3 or j1 + j2 < j3:
                            assert abs(
                                sf.Wigner3j(j1, j2, j3, m1, m2,
                                            -m1 - m2)) < precision_Wigner3j
                        # Test even permutations
                        assert abs(
                            sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                            sf.Wigner3j(j2, j3, j1, m2, -m1 -
                                        m2, m1)) < precision_Wigner3j
                        assert abs(
                            sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                            sf.Wigner3j(j2, j3, j1, m2, -m1 -
                                        m2, m1)) < precision_Wigner3j
                        assert abs(
                            sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                            sf.Wigner3j(j3, j1, j2, -m1 -
                                        m2, m1, m2)) < precision_Wigner3j
                        # Test odd permutations
                        assert abs(
                            sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                            (-1.)**(j1 + j2 + j3) *
                            sf.Wigner3j(j2, j1, j3, m2, m1, -m1 - m2)
                        ) < precision_Wigner3j
                        assert abs(
                            sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                            (-1.)**(j1 + j2 + j3) *
                            sf.Wigner3j(j1, j3, j2, m1, -m1 - m2, m2)
                        ) < precision_Wigner3j
                        # Test sign change
                        assert abs(
                            sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                            (-1.)**(j1 + j2 + j3) *
                            sf.Wigner3j(j1, j2, j3, -m1, -m2, m1 + m2)
                        ) < precision_Wigner3j
                        # Regge symmetries (skip for non-integer values)
                        if ((j2 + j3 - m1) % 2 == 0):
                            assert abs(
                                sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                                sf.Wigner3j(j1, (j2 + j3 - m1) // 2,
                                            (j2 + j3 + m1) // 2, j3 - j2,
                                            (j2 - j3 - m1) // 2 + m1 + m2,
                                            (j2 - j3 + m1) // 2 - m1 -
                                            m2)) < precision_Wigner3j
                        if ((j2 + j3 - m1) % 2 == 0):
                            assert abs(
                                sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                                sf.Wigner3j(j1, (j2 + j3 - m1) // 2,
                                            (j2 + j3 + m1) // 2, j3 - j2,
                                            (j2 - j3 - m1) // 2 + m1 + m2,
                                            (j2 - j3 + m1) // 2 - m1 -
                                            m2)) < precision_Wigner3j
                        if ((j2 + j3 + m1) % 2 == 0 and (j1 + j3 + m2) % 2 == 0
                                and (j1 + j2 - m1 - m2) % 2 == 0):
                            assert (abs(
                                sf.Wigner3j(j1, j2, j3, m1, m2, -m1 - m2) -
                                (-1.)**(j1 + j2 + j3) * sf.Wigner3j(
                                    (j2 + j3 + m1) // 2, (j1 + j3 + m2) // 2,
                                    (j1 + j2 - m1 - m2) // 2, j1 -
                                    (j2 + j3 - m1) // 2, j2 -
                                    (j1 + j3 - m2) // 2, j3 -
                                    (j1 + j2 + m1 + m2) // 2)) <
                                    precision_Wigner3j)