示例#1
0
def test_ndarray_args():
    def f1(a, b, c):
        d = np.asarray(a).copy()
        assert isinstance(
            a, np.ndarray) and not isinstance(a, quaternionic.array)
        assert isinstance(
            b, np.ndarray) and not isinstance(b, quaternionic.array)
        assert isinstance(
            c, np.ndarray) and not isinstance(c, quaternionic.array)
        assert isinstance(
            d, np.ndarray) and not isinstance(d, quaternionic.array)
        return d

    a = quaternionic.array(np.random.rand(17, 3, 4))
    b = quaternionic.array(np.random.rand(13, 3, 4))
    c = quaternionic.array(np.random.rand(11, 3, 4))
    f2 = quaternionic.utilities.ndarray_args(f1)
    d2 = f2(a, b, c)
    assert isinstance(d2,
                      np.ndarray) and not isinstance(d2, quaternionic.array)
    f1.nin = 3
    f3 = quaternionic.utilities.ndarray_args(f1)
    d3 = f3(a, b, c)
    assert isinstance(d3,
                      np.ndarray) and not isinstance(d3, quaternionic.array)
示例#2
0
def test_Wigner_D_underflow(Rs, ell_max, eps):
    # NOTE: This is a delicate test, which depends on the result underflowing exactly when expected.
    # In particular, it should underflow to 0.0 when |mp+m|>32, but should never underflow to 0.0
    # when |mp+m|<32.  So it's not the end of the world if this test fails, but it does mean that
    # the underflow properties have changed, so it might be worth a look.
    epsilon = 1.e-10

    ϵ = 5 * ell_max * eps
    D = np.zeros(sf.WignerDsize(0, ell_max), dtype=complex)
    wigner = sf.Wigner(ell_max)
    ell_mp_m = sf.WignerDrange(0, ell_max)

    # Test |Ra|=1e-10
    R = quaternionic.array(epsilon, 1, 0, 0).normalized
    wigner.D(R, out=D)
    # print(R.to_euler_angles.tolist())
    # print(D.tolist())
    non_underflowing_indices = np.abs(ell_mp_m[:, 1] + ell_mp_m[:, 2]) < 32
    assert np.all(D[non_underflowing_indices] != 0j)
    underflowing_indices = np.abs(ell_mp_m[:, 1] + ell_mp_m[:, 2]) > 32
    assert np.all(D[underflowing_indices] == 0j)

    # Test |Rb|=1e-10
    R = quaternionic.array(1, epsilon, 0, 0).normalized
    wigner.D(R, out=D)
    non_underflowing_indices = np.abs(ell_mp_m[:, 1] - ell_mp_m[:, 2]) < 32
    assert np.all(D[non_underflowing_indices] != 0j)
    underflowing_indices = np.abs(ell_mp_m[:, 1] - ell_mp_m[:, 2]) > 32
    assert np.all(D[underflowing_indices] == 0j)
示例#3
0
def test_pyguvectorize():
    _quaternion_resolution = 10 * np.finfo(float).resolution
    np.random.seed(1234)
    one = quaternionic.array(1, 0, 0, 0)
    x = quaternionic.array(np.random.rand(7, 13, 4))
    y = quaternionic.array(np.random.rand(13, 4))
    z = np.random.rand(13)

    arg0s = [one, -(1 + 2 * _quaternion_resolution) * one, -one, x]

    for k in dir(quaternionic.algebra_ufuncs):
        if not k.startswith('__'):
            f1 = getattr(quaternionic.algebra_ufuncs, k)
            f2 = getattr(quaternionic.algebra, k)
            sig = f2.signature
            inputs = sig.split('->')[0].split(',')
            for arg0 in arg0s:
                args = [arg0.ndarray] if inputs[0] == '(n)' else [
                    z,
                ]
                if len(inputs) > 1:
                    args.append(y.ndarray if inputs[1] == '(n)' else z)
                assert np.allclose(f1(*args),
                                   quaternionic.utilities.pyguvectorize(
                                       f2.types, f2.signature)(f2)(*args),
                                   atol=0.0,
                                   rtol=_quaternion_resolution)
示例#4
0
def test_Wigner_D_non_overflow(ell_max):
    D = np.zeros(sf.WignerDsize(0, ell_max), dtype=complex)
    wigner = sf.Wigner(ell_max)

    # Test |Ra|=1e-10
    R = quaternionic.array(1.e-10, 1, 0, 0).normalized
    assert np.all(np.isfinite(wigner.D(R, out=D)))

    # Test |Rb|=1e-10
    R = quaternionic.array(1, 1.e-10, 0, 0).normalized
    assert np.all(np.isfinite(wigner.D(R, out=D)))
示例#5
0
def Rs():
    np.random.seed(1842)
    ones = [0, -1., 1.]
    rs = [[w, x, y, z] for w in ones for x in ones for y in ones
          for z in ones][1:]
    rs = rs + [
        r for r in [
            quaternionic.array(np.random.uniform(-1, 1, size=4))
            for _ in range(20)
        ]
    ]
    return quaternionic.array(rs).normalized
示例#6
0
def test_basis_multiplication():
    # Basis components
    one, i, j, k = tuple(quaternionic.array(np.eye(4)))

    # Full multiplication table
    assert one * one == one
    assert one * i == i
    assert one * j == j
    assert one * k == k
    assert i * one == i
    assert i * i == np.negative(one)
    assert i * j == k
    assert i * k == -j
    assert j * one == j
    assert j * i == -k
    assert j * j == -one
    assert j * k == i
    assert k * one == k
    assert k * i == j
    assert k * j == -i
    assert k * k == -one

    # Standard expressions
    assert one * one == one
    assert i * i == -one
    assert j * j == -one
    assert k * k == -one
    assert i * j * k == -one
示例#7
0
 def __init__(self , xyz = (0,0,0)):
     self.position = np.asarray(xyz[0:3], dtype=np.float32)
     self.quaternion = quat.array((1,0,0,0))                                                          #quaternion identity
     self.pitch = 0
     self.yaw = 0
     self.scale = np.ones((1,3) , dtype = np.float32)                                                         #scale identity
     self.forward = np.asarray([0,0,-1], dtype=np.float32)
示例#8
0
def test_wigner_evaluate(horner, ell_max_slow, eps):
    import time

    ell_max = max(3, ell_max_slow)
    np.random.seed(1234)
    ϵ = 10 * (2 * ell_max + 1) * eps
    n_theta = n_phi = 2 * ell_max + 1
    max_s = 2
    wigner = sf.Wigner(ell_max, mp_max=max_s)

    max_error = 0.0
    total_time = 0.0
    for rotors in [
            quaternionic.array.from_spherical_coordinates(
                sf.theta_phi(n_theta, n_phi)),
            quaternionic.array(np.random.rand(n_theta, n_phi, 4)).normalized
    ]:

        for s in range(-max_s, max_s + 1):
            ell_min = abs(s)

            a1 = np.random.rand(7,
                                sf.Ysize(ell_min, ell_max) * 2).view(complex)
            a1[:,
               sf.Yindex(ell_min, -ell_min, ell_min):sf.
               Yindex(abs(s), -abs(s), ell_min)] = 0.0

            m1 = sf.Modes(a1, spin_weight=s, ell_min=ell_min, ell_max=ell_max)

            t1 = time.perf_counter()
            f1 = wigner.evaluate(m1, rotors, horner=horner)
            t2 = time.perf_counter()
            assert f1.shape == m1.shape[:-1] + rotors.shape[:-1]
            # print(f"Evaluation for s={s} took {t2-t1:.4f} seconds")
            # print(f1.shape)

            sYlm = np.zeros((sf.Ysize(0, ell_max), ) + rotors.shape[:-1],
                            dtype=complex)
            for i, Rs in enumerate(rotors):
                for j, R in enumerate(Rs):
                    wigner.sYlm(s, R, out=sYlm[:, i, j])
            f2 = np.tensordot(m1.view(np.ndarray), sYlm, axes=([-1], [0]))
            assert f2.shape == m1.shape[:-1] + rotors.shape[:-1]

            assert np.allclose(f1, f2, rtol=ϵ, atol=ϵ), (
                f"max|f1-f2|={np.max(np.abs(f1-f2))} > ϵ={ϵ}\n\n"
                f"s = {s}\n\nrotors = {rotors.tolist()}\n\n"
                # f"f1 = {f1.tolist()}\n\nf2 = {f2.tolist()}"
            )

            max_error = max(np.max(np.abs(f1 - f2)), max_error)
            total_time += t2 - t1

    print()
    print(f"\tmax_error[{horner}] = {max_error}")
    print(f"\ttotal_time[{horner}] = {total_time}")
示例#9
0
def test_sYlm_spin_behavior(Rs, special_angles, ell_max_slow, eps):
    # We expect that the SWSHs behave according to
    # sYlm( R * exp(gamma*z/2) ) = sYlm(R) * exp(-1j*s*gamma)
    # See http://moble.github.io/spherical/SWSHs.html#fn:2
    # for a more detailed explanation
    # print("")
    ϵ = 2 * ell_max_slow * eps
    wigner = sf.Wigner(ell_max_slow)
    for i, R in enumerate(Rs):
        # print("\t{0} of {1}: R = {2}".format(i, len(Rs), R))
        for gamma in special_angles:
            Rgamma = R * quaternionic.array(math.cos(gamma / 2.), 0, 0, math.sin(gamma / 2.))
            for s in range(-ell_max_slow, ell_max_slow + 1):
                Y1 = wigner.sYlm(s, Rgamma)
                Y2 = wigner.sYlm(s, R) * cmath.exp(-1j * s * gamma)
                assert np.allclose(Y1, Y2, atol=ϵ, rtol=ϵ)
示例#10
0
def test_getting_components():
    q = quaternionic.array([1, 2, 3, 4])  # Note the integer input
    assert q.w == 1.0
    assert q.x == 2.0
    assert q.y == 3.0
    assert q.z == 4.0

    assert q.scalar == 1.0
    assert np.array_equal(q.vector, [2.0, 3.0, 4.0])

    assert q.i == 2.0
    assert q.j == 3.0
    assert q.k == 4.0

    assert q.real == 1.0
    assert np.array_equal(q.imag, [2.0, 3.0, 4.0])
示例#11
0
def quaternion_sampler():
    Qs_array = quaternionic.array([
        [np.nan, 0., 0., 0.],
        [np.inf, 0., 0., 0.],
        [-np.inf, 0., 0., 0.],
        [0., 0., 0., 0.],
        [1., 0., 0., 0.],
        [0., 1., 0., 0.],
        [0., 0., 1., 0.],
        [0., 0., 0., 1.],
        [1.1, 2.2, 3.3, 4.4],
        [-1.1, -2.2, -3.3, -4.4],
        [1.1, -2.2, -3.3, -4.4],
        [
            0.18257418583505537115232326093360,
            0.36514837167011074230464652186720,
            0.54772255750516611345696978280080,
            0.73029674334022148460929304373440
        ],
        [
            1.7959088706354, 0.515190292664085, 0.772785438996128,
            1.03038058532817
        ],
        [
            2.81211398529184, -0.392521193481878, -0.588781790222817,
            -0.785042386963756
        ],
    ])
    names = type("QNames", (object, ), dict())()
    names.q_nan1 = 0
    names.q_inf1 = 1
    names.q_minf1 = 2
    names.q_0 = 3
    names.q_1 = 4
    names.x = 5
    names.y = 6
    names.z = 7
    names.Q = 8
    names.Qneg = 9
    names.Qbar = 10
    names.Qnormalized = 11
    names.Qlog = 12
    names.Qexp = 13
    return Qs_array, names
示例#12
0
def test_setting_components():
    q = quaternionic.array([1, 2, 3, 4])  # Note the integer input
    q.w = 5
    q.x = 6
    q.y = 7
    q.z = 8
    assert np.array_equal(q.ndarray, [5.0, 6.0, 7.0, 8.0])

    q.scalar = 1
    q.vector = [2, 3, 4]
    assert np.array_equal(q.ndarray, [1.0, 2.0, 3.0, 4.0])

    q.w = 5
    q.i = 6
    q.j = 7
    q.k = 8
    assert np.array_equal(q.ndarray, [5.0, 6.0, 7.0, 8.0])

    q.real = 1
    q.imag = [2, 3, 4]
    assert np.array_equal(q.ndarray, [1.0, 2.0, 3.0, 4.0])
示例#13
0
    def to_sxs(self):
        """Convert this object to an `sxs.WaveformModes` object

        Note that the resulting object will likely contain references to the same
        underlying data contained in the original object; modifying one will modify the
        other.  You can make a copy of this object *before* calling this function —
        using code like `w.copy().to_sxs` — to obtain separate data.

        """
        import sxs
        import quaternionic
        import quaternion
        from .extrapolation import extrapolate

        # All of these will be stored in the `_metadata` member of the resulting WaveformModes
        # object; most of these will also be accessible directly as attributes.
        kwargs = dict(
            time=self.t,
            time_axis=0,
            modes_axis=1,
            #frame=,  # see below
            spin_weight=self.spin_weight,
            data_type=self.data_type_string.lower(),
            frame_type=self.frame_type_string.lower(),
            history=self.history,
            version_hist=self.version_hist,
            r_is_scaled_out=self.r_is_scaled_out,
            m_is_scaled_out=self.m_is_scaled_out,
            ell_min=self.ell_min,
            ell_max=self.ell_max,
        )

        # If self.frame.size==0, we just don't pass any argument
        if self.frame.size == 1:
            kwargs["frame"] = quaternionic.array(
                [quaternion.as_float_array(self.frame)])
        elif self.frame.size == self.n_times:
            kwargs["frame"] = quaternionic.array(
                quaternion.as_float_array(self.frame))
        elif self.frame.size > 0:
            raise ValueError(
                f"Frame size ({self.frame.size}) should be 0, 1, or "
                f"equal to the number of time steps ({self.n_times})")

        w = sxs.WaveformModes(self.data, **kwargs)

        # Special cases for extrapolate_coord_radii and translation/boost
        if hasattr(self, "extrapolate_coord_radii"):
            w.register_modification(
                extrapolate,
                CoordRadii=list(self.extrapolate_coord_radii),
            )
        if hasattr(self, "space_translation") or hasattr(
                self, "boost_velocity"):
            w.register_modification(
                self.transform,
                space_translation=list(
                    getattr(self, "space_translation", [0., 0., 0.])),
                boost_velocity=list(
                    getattr(self, "boost_velocity", [0., 0., 0.])),
            )

        return w
示例#14
0
def rotate(modes, R):
    """Rotate Modes object by rotor(s)

    Compute fₗₘ = Σₙ fₗₙ 𝔇ˡₙₘ(R), where f is a (possibly spin-weighted) function, fₗₙ are its mode
    weights in the current frame, and fₗₘ are its mode weights in the rotated frame.

    fₗₘ = Σₙ fₗₙ 𝔇ˡₙₘ(R)
        = Σₙ fₗₙ dˡₙₘ(R) exp[iϕₐ(m-n)+iϕₛ(m+n)]
        = Σₙ fₗₙ dˡₙₘ(R) exp[i(ϕₛ+ϕₐ)m+i(ϕₛ-ϕₐ)n]
        = exp[i(ϕₛ+ϕₐ)m] Σₙ fₗₙ dˡₙₘ(R) exp[i(ϕₛ-ϕₐ)n]
        = zₚᵐ Σₙ fₗₙ dˡₙₘ(R) zₘⁿ
        = zₚᵐ {fₗ₀ dˡ₀ₘ(R) + Σₚₙ [fₗₙ dˡₙₘ(R) zₘⁿ + fₗ₋ₙ dˡ₋ₙₘ(R) / zₘⁿ]}
        = zₚᵐ {fₗ₀ ϵ₋ₘ Hˡ₀ₘ(R) + Σₚₙ [fₗₙ ϵₙ ϵ₋ₘ Hˡₙₘ(R) zₘⁿ + fₗ₋ₙ ϵ₋ₙ ϵ₋ₘ Hˡ₋ₙₘ(R) / zₘⁿ]}
        = ϵ₋ₘ zₚᵐ {fₗ₀ Hˡ₀ₘ(R) + Σₚₙ [fₗₙ (-1)ⁿ Hˡₙₘ(R) zₘⁿ + fₗ₋ₙ Hˡ₋ₙₘ(R) / zₘⁿ]}

    Here, n ranges over [-l, l] and pn ranges over [1, l].

    Parameters
    ==========
    modes: Modes
        SWSH modes to rotate
    R: quaternionic.array
        Its shape must satifsy R.shape[:-1] == modes.shape[:-1]

    """
    import quaternionic

    fₗₙ = modes.reshape((np.prod(modes.shape[:-1], dtype=int), modes.shape[-1]))
    fₗₘ = np.zeros_like(modes)
    ell_min = modes.ell_min
    ell_max = modes.ell_max

    if not isinstance(R, quaternionic.array) and R.shape != modes.shape[:-1]:
        raise ValueError(
            f"Input rotor must be either a single quaternion or an array with\n"
            f"shape R.shape={R.shape} == modes.shape[:-1]={modes.shape[:-1]}"
        )

    rotors = quaternionic.array(R).ndarray.reshape((-1, 4))

#unfinished:
    raise NotImplementedError()
    #first_slice = slice(None) if rotors.shape[0]==1 else ?

    for iᵣ in range(rotors.shape[0]):
        zᵦ, zₚ, zₘ = quaternion_angles(rotors[i])

        # Compute H elements (basically Wigner's d functions)
        Hˡₙₘ = H(zᵦ.real, zᵦ.imag, workspace)

        # Pre-compute zₚᵐ=exp[i(ϕₛ+ϕₐ)m] for all values of m
        zₚᵐ = complex_powers(zₚ, ell_max)

        for ell in range(ell_min, ell_max+1):
            for m in range(-ell_max, ell_max+1):
                # fₗₘ = ϵ₋ₘ zₚᵐ {fₗ₀ Hˡ₀ₘ(R) + Σₚₙ [fₗ₋ₙ Hˡ₋ₙₘ(R) / zₘⁿ + fₗₙ (-1)ⁿ Hˡₙₘ(R) zₘⁿ]}
                iₘ = fₗₘ.index(ell, m)

                # Initialize with n=0 term
                fₗₘ[first_slice, iₘ] = fₗₙ[fₗₙ.index(ell, 0)] * Hˡₙₘ.element(ell, 0, m)

                # Compute dˡₙₘ terms recursively for 0<n<l, using symmetries for negative n, and
                # simultaneously add the mode weights times zₘⁿ=exp[i(ϕₛ-ϕₐ)n] to the result using
                # Horner form
                negative_terms[first_slice] = fₗₙ[first_slice, fₗₙ.index(ell, -ell)] * Hˡₙₘ.element(ell, -ell, m)
                positive_terms[first_slice] = fₗₙ[first_slice, fₗₙ.index(ell, ell)] * Hˡₙₘ.element(ell, ell, m) * (-1)**ell
                for n in range(ell-1, 0, -1):
                    negative_terms /= zₘ
                    negative_terms += fₗₙ[first_slice, fₗₙ.index(ell, -n)] * Hˡₙₘ.element(ell, -n, m)
                    positive_terms *= zₘ
                    positive_terms += fₗₙ[first_slice, fₗₙ.index(ell, n)] * Hˡₙₘ.element(ell, n, m) * (-1)**n
                fₗₘ[first_slice, iₘ] += negative_terms / zₘ
                fₗₘ[first_slice, iₘ] += positive_terms * zₘ

                # Finish calculation of fₗₘ by multiplying by zₚᵐ=exp[i(ϕₛ+ϕₐ)m]
                fₗₘ[first_slice, iₘ] *= ϵ(-m) * zₚᵐ[m]

    fₗₘ = fₗₘ.reshape(modes.shape)
    return fₗₘ
示例#15
0
 def prepare_unpickle(self):
     self.player.transform.quaternion = quat.array(self.player.transform.quaternion)
     for e in self.entities:
         e.transform.quaternion = quat.array(e.transform.quaternion)
示例#16
0
文件: wigner.py 项目: moble/spherical
    def D(self, R, out=None, workspace=None):
        """Compute Wigner's 𝔇 matrix

        Parameters
        ----------
        R : array_like
            Array to be interpreted as a quaternionic array (thus its final dimension
            must have size 4), representing the rotations on which the 𝔇 matrix will be
            evaluated.
        out : array_like, optional
            Array into which the 𝔇 values should be written.  It should be an array of
            complex, with size `self.Dsize`.  If not present, the array will be
            created.  In either case, the array will also be returned.
        workspace : array_like, optional
            A working array like the one returned by Wigner.new_workspace().  If not
            present, this object's default workspace will be used.  Note that it is not
            safe to use the same workspace on multiple threads.

        Returns
        -------
        D : array
            This is a 1-dimensional array of complex; see below.

        See Also
        --------
        H : Compute a portion of the H matrix
        d : Compute the full Wigner d matrix
        rotate : Avoid computing the full 𝔇 matrix and rotate modes directly
        evaluate : Avoid computing the full 𝔇 matrix and evaluate modes directly

        Notes
        -----
        This function is the preferred method of computing the 𝔇 matrix for large ell
        values.  In particular, above ell≈32 standard formulas become completely
        unusable because of numerical instabilities and overflow.  This function uses
        stable recursion methods instead, and should be usable beyond ell≈1000.

        This function computes 𝔇ˡₘₚ,ₘ(R).  The result is returned in a 1-dimensional
        array ordered as

            [
                𝔇(ell, mp, m, R)
                for ell in range(ell_max+1)
                for mp in range(-min(ℓ, mp_max), min(ℓ, mp_max)+1)
                for m in range(-ell, ell+1)
            ]

        """
        if self.mp_max < self.ell_max:
            raise ValueError(
                f"Cannot compute full 𝔇 matrix up to ell_max={self.ell_max} if mp_max is only {self.mp_max}"
            )
        if out is not None and out.size != (self.Dsize * R.size // 4):
            raise ValueError(
                f"Given output array has size {out.size}; it should be {self.Dsize * R.size // 4}"
            )
        if out is not None and out.dtype != complex:
            raise ValueError(f"Given output array has dtype {out.dtype}; it should be complex")

        if workspace is not None:
            Hwedge, Hv, Hextra, zₐpowers, zᵧpowers, z = self._split_workspace(workspace)
        else:
            Hwedge, Hv, Hextra, zₐpowers, zᵧpowers, z = (
                self.Hwedge, self.Hv, self.Hextra, self.zₐpowers, self.zᵧpowers, self.z
            )

        quaternions = quaternionic.array(R).ndarray.reshape((-1, 4))
        function_values = (
            out.reshape(quaternions.shape[0], self.Dsize)
            if out is not None
            else np.zeros(quaternions.shape[:-1] + (self.Dsize,), dtype=complex)
        )

        # Loop over all input quaternions
        for i_R in range(quaternions.shape[0]):
            to_euler_phases(quaternions[i_R], z)
            Hwedge = self.H(z[1], Hwedge, Hv, Hextra)
            𝔇 = function_values[i_R]
            _complex_powers(z[0:1], self.ell_max, zₐpowers)
            _complex_powers(z[2:3], self.ell_max, zᵧpowers)
            _fill_wigner_D(self.ell_min, self.ell_max, self.mp_max, 𝔇, Hwedge, zₐpowers[0], zᵧpowers[0])
        return function_values.reshape(R.shape[:-1] + (self.Dsize,))
示例#17
0
def test_rotate_vectors(Rs):
    one, x, y, z = tuple(quaternionic.array(np.eye(4)))
    zero = 0.0 * one

    with pytest.raises(ValueError):
        one.rotate(np.array(3.14))
    with pytest.raises(ValueError):
        one.rotate(np.random.rand(17, 9, 4))
    with pytest.raises(ValueError):
        one.rotate(np.random.rand(17, 9, 3), axis=1)

    np.random.seed(1234)
    # Test (1)*(1)
    vecs = np.random.rand(3)
    quats = z
    vecsprime = quats.rotate(vecs)
    assert np.allclose(vecsprime, (quats * quaternionic.array(0, *vecs) *
                                   quats.inverse).vector,
                       rtol=0.0,
                       atol=0.0)
    assert quats.shape[:-1] + vecs.shape == vecsprime.shape, ("Out of shape!",
                                                              quats.shape,
                                                              vecs.shape,
                                                              vecsprime.shape)
    # Test (1)*(5)
    vecs = np.random.rand(5, 3)
    quats = z
    vecsprime = quats.rotate(vecs)
    for i, vec in enumerate(vecs):
        assert np.allclose(vecsprime[i], (quats * quaternionic.array(0, *vec) *
                                          quats.inverse).vector,
                           rtol=0.0,
                           atol=0.0)
    assert quats.shape[:-1] + vecs.shape == vecsprime.shape, ("Out of shape!",
                                                              quats.shape,
                                                              vecs.shape,
                                                              vecsprime.shape)
    # Test (1)*(5) inner axis
    vecs = np.random.rand(3, 5)
    quats = z
    vecsprime = quats.rotate(vecs, axis=-2)
    for i, vec in enumerate(vecs.T):
        assert np.allclose(vecsprime[:, i],
                           (quats * quaternionic.array(0, *vec) *
                            quats.inverse).vector,
                           rtol=0.0,
                           atol=0.0)
    assert quats.shape[:-1] + vecs.shape == vecsprime.shape, ("Out of shape!",
                                                              quats.shape,
                                                              vecs.shape,
                                                              vecsprime.shape)
    # Test (N)*(1)
    vecs = np.random.rand(3)
    quats = Rs
    vecsprime = quats.rotate(vecs)
    assert np.allclose(vecsprime, [
        vprime.vector
        for vprime in quats * quaternionic.array(0, *vecs) * ~quats
    ],
                       rtol=1e-15,
                       atol=1e-15)
    assert quats.shape[:-1] + vecs.shape == vecsprime.shape, ("Out of shape!",
                                                              quats.shape,
                                                              vecs.shape,
                                                              vecsprime.shape)
    # Test (N)*(5)
    vecs = np.random.rand(5, 3)
    quats = Rs
    vecsprime = quats.rotate(vecs)
    for i, vec in enumerate(vecs):
        assert np.allclose(vecsprime[:, i], [
            vprime.vector
            for vprime in quats * quaternionic.array(0, *vec) * ~quats
        ],
                           rtol=1e-15,
                           atol=1e-15)
    assert quats.shape[:-1] + vecs.shape == vecsprime.shape, ("Out of shape!",
                                                              quats.shape,
                                                              vecs.shape,
                                                              vecsprime.shape)
    # Test (N)*(5) inner axis
    vecs = np.random.rand(3, 5)
    quats = Rs
    vecsprime = quats.rotate(vecs, axis=-2)
    for i, vec in enumerate(vecs.T):
        assert np.allclose(vecsprime[:, :, i], [
            vprime.vector
            for vprime in quats * quaternionic.array(0, *vec) * ~quats
        ],
                           rtol=1e-15,
                           atol=1e-15)
    assert quats.shape[:-1] + vecs.shape == vecsprime.shape, ("Out of shape!",
                                                              quats.shape,
                                                              vecs.shape,
                                                              vecsprime.shape)
示例#18
0
def test_iterator():
    a = np.arange(17 * 3 * 4).reshape((17, 3, 4))
    q = quaternionic.array(a)
    for i, qi in enumerate(q.iterator):
        assert np.array_equal(qi, np.arange(4) + 4.0 * i)
示例#19
0
文件: wigner.py 项目: moble/spherical
    def sYlm(self, s, R, out=None, workspace=None):
        """Evaluate (possibly spin-weighted) spherical harmonic

        Parameters
        ----------
        R : array_like
            Array to be interpreted as a quaternionic array (thus its final dimension
            must have size 4), representing the rotations on which the sYlm will be
            evaluated.
        out : array_like, optional
            Array into which the d values should be written.  It should be an array of
            complex, with size `self.Ysize`.  If not present, the array will be
            created.  In either case, the array will also be returned.
        workspace : array_like, optional
            A working array like the one returned by Wigner.new_workspace().  If not
            present, this object's default workspace will be used.  Note that it is not
            safe to use the same workspace on multiple threads.

        Returns
        -------
        Y : array
            This is a 1-dimensional array of complex; see below.

        See Also
        --------
        H : Compute a portion of the H matrix
        d : Compute the full Wigner d matrix
        D : Compute the full Wigner 𝔇 matrix
        rotate : Avoid computing the full 𝔇 matrix and rotate modes directly
        evaluate : Avoid computing the full 𝔇 matrix and evaluate modes directly

        Notes
        -----
        The spherical harmonics of spin weight s are related to the 𝔇 matrix as

            ₛYₗₘ(R) = (-1)ˢ √((2ℓ+1)/(4π)) 𝔇ˡₘ₋ₛ(R)
                   = (-1)ˢ √((2ℓ+1)/(4π)) 𝔇̄ˡ₋ₛₘ(R̄)

        This function is the preferred method of computing the sYlm for large ell
        values.  In particular, above ell≈32 standard formulas become completely
        unusable because of numerical instabilities and overflow.  This function uses
        stable recursion methods instead, and should be usable beyond ell≈1000.

        This function computes ₛYₗₘ(R).  The result is returned in a 1-dimensional
        array ordered as

            [
                Y(s, ell, m, R)
                for ell in range(ell_max+1)
                for m in range(-ell, ell+1)
            ]

        """
        if abs(s) > self.mp_max:
            raise ValueError(
                f"This object has mp_max={self.mp_max}, which is not "
                f"sufficient to compute sYlm values for spin weight s={s}"
            )
        if out is not None and out.size != (self.Ysize * R.size // 4):
            raise ValueError(
                f"Given output array has size {out.size}; it should be {self.Ysize * R.size // 4}"
            )
        if out is not None and out.dtype != complex:
            raise ValueError(f"Given output array has dtype {out.dtype}; it should be complex")

        if workspace is not None:
            Hwedge, Hv, Hextra, zₐpowers, zᵧpowers, z = self._split_workspace(workspace)
        else:
            Hwedge, Hv, Hextra, zₐpowers, zᵧpowers, z = (
                self.Hwedge, self.Hv, self.Hextra, self.zₐpowers, self.zᵧpowers, self.z
            )

        quaternions = quaternionic.array(R).ndarray.reshape((-1, 4))
        function_values = (
            out.reshape(quaternions.shape[0], self.Ysize)
            if out is not None
            else np.zeros(quaternions.shape[:-1] + (self.Ysize,), dtype=complex)
        )

        # Loop over all input quaternions
        for i_R in range(quaternions.shape[0]):
            to_euler_phases(quaternions[i_R], z)
            Hwedge = self.H(z[1], Hwedge, Hv, Hextra)
            Y = function_values[i_R]
            _complex_powers(z[0:1], self.ell_max, zₐpowers)
            zᵧpower = z[2]**abs(s)
            _fill_sYlm(self.ell_min, self.ell_max, self.mp_max, s, Y, Hwedge, zₐpowers[0], zᵧpower)

        return function_values.reshape(R.shape[:-1] + (self.Ysize,))
示例#20
0
文件: wigner.py 项目: moble/spherical
    def rotate(self, modes, R, out=None, workspace=None, horner=False):
        """Rotate Modes object

        Parameters
        ----------
        modes : Modes object
        R : quaternionic.array
            Unit quaternion representing the rotation of the frame in which the mode
            weights are measured.
        out : array_like, optional
            Array into which the rotated mode weights should be written.  It should be
            an array of complex with the same shape as `modes`.  If not present, the
            array will be created.  In either case, the array will also be returned.
        workspace : array_like, optional
            A working array like the one returned by Wigner.new_workspace().  If not
            present, this object's default workspace will be used.  Note that it is not
            safe to use the same workspace on multiple threads.
        horner : bool, optional
            If False (the default), rotation will be done using matrix multiplication
            with Wigner's 𝔇 — which will typically use BLAS, and thus be as fast as
            possible.  If True, the result will be built up using Horner form, which
            should be more accurate, but may be significantly slower.

        Returns
        -------
        rotated_modes : array_like
            This array holds the complex function values.  Its shape is
            modes.shape[:-1]+R.shape[:-1].

        """
        if self.mp_max < self.ell_max:
            raise ValueError(
                f"Cannot rotate modes up to ell_max={self.ell_max} if mp_max is only {self.mp_max}"
            )

        ell_min = modes.ell_min
        ell_max = modes.ell_max
        spin_weight = modes.spin_weight

        if ell_max > self.ell_max:
            raise ValueError(
                f"This object has ell_max={self.ell_max}, which is not "
                f"sufficient for the input modes object with ell_max={ell_max}"
            )

        # Reinterpret inputs as 2-d np.arrays
        mode_weights = modes.ndarray.reshape((-1, modes.shape[-1]))
        R = quaternionic.array(R)

        # Construct storage space
        rotated_mode_weights = (
            out
            if out is not None
            else np.zeros_like(mode_weights)
        )

        if horner:
            if workspace is not None:
                Hwedge, Hv, Hextra, _, _, z = self._split_workspace(workspace)
            else:
                Hwedge, Hv, Hextra, z = self.Hwedge, self.Hv, self.Hextra, self.z
            to_euler_phases(R, z)
            Hwedge = self.H(z[1], Hwedge, Hv, Hextra)
            _rotate_Horner(
                mode_weights, rotated_mode_weights,
                self.ell_min, self.ell_max, self.mp_max,
                ell_min, ell_max, spin_weight,
                Hwedge, z[0], z[2]
            )
        else:
            D = self.D(R, workspace)
            _rotate(
                mode_weights, rotated_mode_weights,
                self.ell_min, self.ell_max, self.mp_max,
                ell_min, ell_max, spin_weight,
                D
            )

        return type(modes)(
            rotated_mode_weights.reshape(modes.shape),
            **modes._metadata
        )
示例#21
0
文件: wigner.py 项目: moble/spherical
    def evaluate(self, modes, R, out=None, workspace=None, horner=False):
        """Evaluate Modes object as function of rotations

        Parameters
        ----------
        modes : Modes object
        R : quaternionic.array
            Arbitrarily shaped array of quaternions.  All modes in the input will be
            evaluated on each of these quaternions.  Note that it is fairly standard to
            construct these quaternions from spherical coordinates, as with the
            function `quaternionic.array.from_spherical_coordinates`.
        out : array_like, optional
            Array into which the function values should be written.  It should be an
            array of complex, with shape `modes.shape[:-1]+R.shape[:-1]`.  If not
            present, the array will be created.  In either case, the array will also be
            returned.
        workspace : array_like, optional
            A working array like the one returned by Wigner.new_workspace().  If not
            present, this object's default workspace will be used.  Note that it is not
            safe to use the same workspace on multiple threads.
        horner : bool, optional
            If False (the default), evaluation will be done using vector multiplication
            with sYlm — which will typically use BLAS, and thus be as fast as possible.
            If True, the result will be built up using Horner form, which should be
            more accurate, but may be significantly slower.

        Returns
        -------
        f : array_like
            This array holds the complex function values.  Its shape is
            modes.shape[:-1]+R.shape[:-1].

        """
        spin_weight = modes.spin_weight
        ell_min = modes.ell_min
        ell_max = modes.ell_max

        if abs(spin_weight) > self.mp_max:
            raise ValueError(
                f"This object has mp_max={self.mp_max}, which is not "
                f"sufficient to compute sYlm values for spin weight s={spin_weight}"
            )

        if max(abs(spin_weight), ell_min) < self.ell_min:
            raise ValueError(
                f"This object has ell_min={self.ell_min}, which is not "
                f"sufficient for the requested spin weight s={spin_weight} and ell_min={ell_min}"
            )

        if ell_max > self.ell_max:
            raise ValueError(
                f"This object has ell_max={self.ell_max}, which is not "
                f"sufficient for the input modes object with ell_max={ell_max}"
            )

        # Reinterpret inputs as 2-d np.arrays
        mode_weights = modes.ndarray.reshape((-1, modes.shape[-1]))
        quaternions = quaternionic.array(R).ndarray.reshape((-1, 4))

        # Construct storage space
        function_values = (
            out
            if out is not None
            else np.zeros(mode_weights.shape[:-1] + quaternions.shape[:-1], dtype=complex)
        )

        if horner:
            if workspace is not None:
                Hwedge, Hv, Hextra, _, _, z = self._split_workspace(workspace)
            else:
                Hwedge, Hv, Hextra, z = self.Hwedge, self.Hv, self.Hextra, self.z

            # Loop over all input quaternions
            for i_R in range(quaternions.shape[0]):
                to_euler_phases(quaternions[i_R], z)
                Hwedge = self.H(z[1], Hwedge, Hv, Hextra)
                _evaluate_Horner(
                    mode_weights, function_values[..., i_R],
                    self.ell_min, self.ell_max, self.mp_max,
                    ell_min, ell_max, spin_weight,
                    Hwedge, z[0], z[2]
                )

        else:
            Y = np.zeros(self.Ysize, dtype=complex)

            # Loop over all input quaternions
            for i_R in range(quaternions.shape[0]):
                self.sYlm(spin_weight, quaternions[i_R], out=Y, workspace=workspace)
                np.matmul(mode_weights, Y, out=function_values[..., i_R])

        return function_values.reshape(modes.shape[:-1] + R.shape[:-1])