Пример #1
0
def test_vector_cross():
    v1 = Vector([2, 3])
    v2 = Vector([1, 7])
    assert v1.cross(v2) == Vector([11])

    v3 = Vector([2, 7, 4])
    v4 = Vector([3, 9, 8])
    assert v3.cross(v4) == Vector([20, -4, -3])

    assert v1.cross(v3) == Vector([12, -8, 8])
    assert v4.cross(v2) == Vector([-56, 8, 12])
Пример #2
0
class Quaternion(CopyableMixin):
    def __init__(self, w: Real = 1, x: Real = 0, y: Real = 0, z: Real = 0):
        self.scalar = w
        self.vector = Vector([x, y, z])

    @property
    def w(self) -> Real:
        return self.scalar

    @w.setter
    def w(self, value: Real) -> None:
        self.scalar = value

    @property
    def x(self) -> Real:
        return self.vector[0]

    @x.setter
    def x(self, value: Real) -> None:
        self.vector[0] = value

    @property
    def y(self) -> Real:
        return self.vector[1]

    @y.setter
    def y(self, value: Real) -> None:
        self.vector[1] = value

    @property
    def z(self) -> Real:
        return self.vector[2]

    @z.setter
    def z(self, value: Real) -> None:
        self.vector[2] = value

    def __iter__(self) -> Generator[Real, None, None]:
        yield self.scalar
        yield from self.vector

    def __str__(self) -> str:
        return f"{self.__class__.__name__}(w={self.w:.4f}, x={self.x:.4f}, y={self.y:.4f}, z={self.z:.4f})"

    def __repr__(self) -> str:
        return self.__str__()

    def __neg__(self) -> "Quaternion":
        return Quaternion(*(-i for i in self))

    def __add__(self, other: "Quaternion") -> "Quaternion":
        if not isinstance(other, Quaternion):
            raise TypeError(
                f"Cannot add instances of type {type(other)} and {type(self)}")
        return Quaternion(self.scalar + other.scalar,
                          *(self.vector + other.vector))

    def __sub__(self, other: "Quaternion") -> "Quaternion":
        if not isinstance(other, Quaternion):
            raise TypeError(
                f"Cannot subtract instances of type {type(other)} and {type(self)}"
            )
        return Quaternion(self.scalar - other.scalar,
                          *(self.vector - other.vector))

    def __div__(self, other: "Quaternion") -> "Quaternion":
        if not isinstance(other, Quaternion):
            other = Quaternion.from_scalar(other)
        if other.is_zero_quaternion():
            raise ZeroDivisionError("other is a zero quaternion!")
        return self * other.inverse()

    def __idiv__(self, other: "Quaternion") -> "Quaternion":
        return self.__div__(other)

    def __rdiv__(self, other: "Quaternion") -> "Quaternion":
        if not isinstance(other, Quaternion):
            other = Quaternion.from_scalar(other)
        return other * self.inverse()

    def __truediv__(self, other: "Quaternion") -> "Quaternion":
        return self.__div__(other)

    def __itruediv__(self, other: "Quaternion") -> "Quaternion":
        return self.__idiv__(other)

    def __rtruediv__(self, other: "Quaternion") -> "Quaternion":
        return self.__rdiv__(other)

    def __mul__(self, other: "Quaternion") -> "Quaternion":
        if not isinstance(other, Quaternion):
            other = Quaternion.from_scalar(other)

        _scalar = self.scalar * other.scalar - self.vector.dot(other.vector)
        _vector = other.vector.scale(self.scalar) + self.vector.scale(
            other.scalar) + self.vector.cross(other.vector)
        return Quaternion(_scalar, *_vector)

    def __imul__(self, other: "Quaternion") -> "Quaternion":
        return self * other

    def __rmul__(self, other: "Quaternion") -> "Quaternion":
        if not isinstance(other, Quaternion):
            other = Quaternion.from_scalar(other)
        return other * self

    def __pow__(self, exponent: Real) -> "Quaternion":
        norm = self.norm()
        if norm > 0:
            vector_mag = self.vector.magnitude()
            if vector_mag <= 0:
                return Quaternion.from_scalar(self.scalar**exponent)
            unit_vector = self.vector.scale(1 / vector_mag)
            phi = acos(self.scalar / norm)
            _scalar = cos(exponent * phi)
            _vector = unit_vector.scale(sin(exponent * phi))
            return (norm**exponent) * Quaternion(_scalar, *_vector)
        return self.copy()

    def __eq__(self, other: "Quaternion") -> "Quaternion":
        return all(
            isclose(i, j, rel_tol=1e-09, abs_tol=1e-09)
            for i, j in zip(self, other))

    def __hash__(self) -> int:
        return hash(self.as_tuple())

    def is_zero_quaternion(self) -> bool:
        return self == Quaternion(0, 0, 0, 0)

    def is_unit_quaternion(self) -> bool:
        return isclose(self._squared_sum(), 1.0, rel_tol=1e-09)

    def magnitude(self) -> Real:
        return self.norm()

    def _squared_sum(self) -> Real:
        return self.scalar**2 + self.vector.dot(self.vector)

    def norm(self) -> Real:
        return sqrt(self._squared_sum())

    def __len__(self):
        return 4

    def __getitem__(self, idx: int) -> Real:
        if idx > 3:
            raise IndexError(
                f"Index {idx} is out of range for a Quaternion of size 4")
        return self.scalar if idx == 0 else self.vector[idx - 1]

    def __setitem__(self, idx: int, value: Real) -> None:
        if idx > 3:
            raise IndexError(
                f"Index {idx} is out of range for a Quaternion of size 4")
        if idx == 0:
            self.scalar = value
        else:
            self.vector[idx - 1] = value

    def normalize(self) -> "Quaternion":
        if self.is_zero_quaternion():
            raise ValueError("Cannot normalize a zero quaternion!")
        n = self.norm()
        return Quaternion(*(i / n for i in self))

    def inverse(self) -> "Quaternion":
        if self.is_zero_quaternion():
            raise ValueError("Cannot invert a zero quaternion!")
        square_sum = self._squared_sum()
        return Quaternion(self.scalar / square_sum,
                          *-self.vector.scale(1 / square_sum))

    def conjugate(self) -> "Quaternion":
        return Quaternion(self.scalar, -self.vector)

    def as_tuple(self) -> Tuple[Real]:
        return tuple(i for i in self)

    @classmethod
    def from_tuple(cls, tup: Tuple[Real]) -> "Quaternion":
        assert len(tup) == 4
        return cls(*tup)

    @classmethod
    def identity(cls) -> "Quaternion":
        return cls(1, 0, 0, 0)

    @classmethod
    def from_scalar(cls, scalar: Real) -> "Quaternion":
        return cls(scalar, 0, 0, 0)
Пример #3
0
class VectorCase(unittest.TestCase):
    
    def setUp(self):
        
        self.a = Vector(1.0, 1.0, 1.0)
        self.b = Vector(1.0, 0.0, 0.0)
        self.c = Vector(0.0, 0.0, 1.0)
    
    def tearDown(self):
        
        del self.a
        del self.b
        del self.c
    
    def test_richmap(self):
        

        
        self.assertTrue(self.a==self.a)     # Test equality
        self.assertFalse(self.a==self.b)    # Test equality
        
        self.assertTrue(self.a!=self.b)     # Test unequality
        
        self.assertRaises(TypeError, operator.gt, self.a, self.b)  # Test not implemented operators.

        self.assertFalse(self.a==3.0)   # Test other type
        self.assertFalse(3.0==self.a)   # Test other type
    
    def test_arithmetics(self):
        
        """add and mul"""
        self.assertAlmostEqual( self.a + self.a, 2.0 * self.a)
        
        """sub"""
        self.assertAlmostEqual( self.a - self.a, Vector(0.0, 0.0, 0.0))
        
        """div and truediv"""
        self.assertAlmostEqual( self.a / 2.0, Vector(0.5, 0.5, 0.5))
        
        """pow"""
        
        """neg"""
        self.assertEqual( -self.a, Vector(-1.0, -1.0, -1.0))
        
    
    def test_dot(self):
        
        self.assertEqual( self.a.dot(self.b) , 1.0)
        self.assertEqual( self.b.dot(self.a) , 1.0)
    
    def test_cross(self):
        
        self.assertEqual( self.a.cross(self.b), Vector(0.0, +1.0, -1.0) )
        self.assertEqual( self.b.cross(self.a), Vector(0.0, -1.0, +1.0) )
        
        self.assertEqual( self.b.cross(self.c), Vector(0.0, -1.0, 0.0) )
        self.assertEqual( self.c.cross(self.b), Vector(0.0, +1.0, 0.0) )
        
        self.assertEqual( self.a.cross(self.c), Vector(+1.0, -1.0, 0.0) )
        self.assertEqual( self.c.cross(self.a), Vector(-1.0, +1.0, 0.0) )
        
    def test_norm(self):
        
        # Comparing floating point numbers.
        self.assertAlmostEqual(self.a.norm(), 3.0**(0.5))
        self.assertAlmostEqual(self.b.norm(), 1.0)
        self.assertAlmostEqual(self.c.norm(), 1.0)
    
    def test_normal(self):
        
        self.assertIs(type(self.a.normalize()), Vector)   # Check that we get a vector back.
        
        self.assertEqual(self.b, self.b.normalize())
        
        self.assertAlmostEqual(self.a.normalize(), self.a/self.a.norm())
    
    
    def test_cosines_with(self):
        
        self.assertAlmostEqual(self.b.cosines_with(self.c), 0.0) # Vectors are orthogonal, so cos(90)=0
        self.assertAlmostEqual(self.b.cosines_with(self.b), 1.0) # Vectors are parralel