From a3eea78233ff7823591915ec815ae60842db24bc Mon Sep 17 00:00:00 2001 From: Luciano Ramalho Date: Fri, 17 Oct 2014 22:26:39 -0300 Subject: [PATCH] last example of chapter 9: classes/vector_v5.py --- classes/mem_test.py | 8 +- classes/multivector_v1.py | 124 ---------- classes/multivector_v1_hash.py | 123 ---------- classes/multivector_v2.py | 164 ------------- classes/multivector_v3.py | 198 ---------------- classes/spherical-coordinates.txt | 48 ++++ classes/test_vector_spherical.py | 37 +++ classes/{vector_v0.py => vector2d_v0.py} | 20 +- classes/vector2d_v1.py | 85 +++++++ classes/vector2d_v2.py | 118 +++++++++ ..._snippet.py => vector2d_v2_fmt_snippet.py} | 34 +-- classes/vector2d_v3.py | 156 ++++++++++++ ...ector_v3_slots.py => vector2d_v3_slots.py} | 52 ++-- classes/vector_v1.py | 111 ++++++--- classes/vector_v2.py | 146 ++++++++---- classes/vector_v3.py | 223 +++++++++++------- classes/{multivector_v4.py => vector_v4.py} | 76 +++--- classes/{multivector_v5.py => vector_v5.py} | 147 ++++++------ 18 files changed, 923 insertions(+), 947 deletions(-) delete mode 100644 classes/multivector_v1.py delete mode 100644 classes/multivector_v1_hash.py delete mode 100644 classes/multivector_v2.py delete mode 100644 classes/multivector_v3.py create mode 100644 classes/spherical-coordinates.txt create mode 100644 classes/test_vector_spherical.py rename classes/{vector_v0.py => vector2d_v0.py} (75%) create mode 100644 classes/vector2d_v1.py create mode 100644 classes/vector2d_v2.py rename classes/{vector_v2_fmt_snippet.py => vector2d_v2_fmt_snippet.py} (75%) create mode 100644 classes/vector2d_v3.py rename classes/{vector_v3_slots.py => vector2d_v3_slots.py} (73%) rename classes/{multivector_v4.py => vector_v4.py} (69%) rename classes/{multivector_v5.py => vector_v5.py} (56%) diff --git a/classes/mem_test.py b/classes/mem_test.py index e75ce03..ecf6c04 100644 --- a/classes/mem_test.py +++ b/classes/mem_test.py @@ -11,13 +11,13 @@ else: print('Usage: {} '.format()) sys.exit(1) -fmt = 'Selected Vector type: {.__name__}.{.__name__}' -print(fmt.format(module, module.Vector)) +fmt = 'Selected Vector2d type: {.__name__}.{.__name__}' +print(fmt.format(module, module.Vector2d)) mem_init = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss -print('Creating {:,} Vector instances'.format(NUM_VECTORS)) +print('Creating {:,} Vector2d instances'.format(NUM_VECTORS)) -vectors = [module.Vector(3.0, 4.0) for i in range(NUM_VECTORS)] +vectors = [module.Vector2d(3.0, 4.0) for i in range(NUM_VECTORS)] mem_final = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss print('Initial RAM usage: {:14,}'.format(mem_init)) diff --git a/classes/multivector_v1.py b/classes/multivector_v1.py deleted file mode 100644 index df07954..0000000 --- a/classes/multivector_v1.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -A multi-dimensional ``MultiVector`` class, take 1 - -A ``MultiVector`` is built from an iterable of numbers:: - - >>> MultiVector([3.1, 4.2]) - MultiVector([3.1, 4.2]) - >>> MultiVector((3, 4, 5)) - MultiVector([3.0, 4.0, 5.0]) - >>> MultiVector(range(10)) - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - - -Tests with 2-dimensions (same results as ``vector_v1.py``):: - - >>> v1 = MultiVector([3, 4]) - >>> x, y = v1 - >>> x, y - (3.0, 4.0) - >>> v1 - MultiVector([3.0, 4.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0) - >>> octets = bytes(v1) - >>> octets - b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' - >>> abs(v1) - 5.0 - >>> bool(v1), bool(MultiVector([0, 0])) - (True, False) - - -Test of ``.frombytes()`` class method: - - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0]) - >>> v1 == v1_clone - True - - -Tests with 3-dimensions:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> x, y, z = v1 - >>> x, y, z - (3.0, 4.0, 5.0) - >>> v1 - MultiVector([3.0, 4.0, 5.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0, 5.0) - >>> abs(v1) # doctest:+ELLIPSIS - 7.071067811... - >>> bool(v1), bool(MultiVector([0, 0, 0])) - (True, False) - - -Tests with many dimensions:: - - >>> v7 = MultiVector(range(7)) - >>> v7 - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - >>> abs(v7) # doctest:+ELLIPSIS - 9.53939201... - - -Test of ``.__bytes__`` and ``.frombytes()`` methods:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0, 5.0]) - >>> v1 == v1_clone - True - - -""" - -# BEGIN MULTIVECTOR_V1 -from array import array -import reprlib -import math - - -class MultiVector: - typecode = 'd' - - def __init__(self, components): - self._components = array(self.typecode, components) # <1> - - def __iter__(self): - return iter(self._components) # <2> - - def __repr__(self): - components = reprlib.repr(self._components) # <3> - components = components[components.find('['):-1] # <4> - return 'MultiVector({})'.format(components) - - def __str__(self): - return str(tuple(self)) - - def __bytes__(self): - return bytes(self._components) # <5> - - def __eq__(self, other): - return tuple(self) == tuple(other) - - def __abs__(self): - return math.sqrt(sum(x * x for x in self)) # <6> - - def __bool__(self): - return bool(abs(self)) - - @classmethod - def frombytes(cls, octets): - memv = memoryview(octets).cast(cls.typecode) - return cls(memv) # <7> -# END MULTIVECTOR_V1 diff --git a/classes/multivector_v1_hash.py b/classes/multivector_v1_hash.py deleted file mode 100644 index 3e05eb5..0000000 --- a/classes/multivector_v1_hash.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -A multi-dimensional ``MultiVector`` class, take 1 - -Tests with 2-dimensions (same results as ``vector_v1.py``):: - - >>> v1 = MultiVector([3, 4]) - >>> x, y = v1 - >>> x, y - (3.0, 4.0) - >>> v1 - MultiVector([3.0, 4.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0) - >>> octets = bytes(v1) - >>> octets - b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' - >>> abs(v1) - 5.0 - >>> bool(v1), bool(MultiVector([0, 0])) - (True, False) - -Test of ``.frombytes()`` class method: - - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0]) - >>> v1 == v1_clone - True - -Tests with 3-dimensions:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> x, y, z = v1 - >>> x, y, z - (3.0, 4.0, 5.0) - >>> v1 - MultiVector([3.0, 4.0, 5.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0, 5.0) - >>> abs(v1) # doctest:+ELLIPSIS - 7.071067811... - >>> bool(v1), bool(MultiVector([0, 0, 0])) - (True, False) - -Tests with many dimensions:: - - >>> v7 = MultiVector(range(7)) - >>> v7 - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) - >>> abs(v7) # doctest:+ELLIPSIS - 9.53939201... - -Test of ``.__bytes__`` and ``.frombytes()`` methods:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0, 5.0]) - >>> v1 == v1_clone - True - -Tests of hashing:: - - >>> v1 = MultiVector([3, 4]) - >>> v2 = MultiVector([3.1, 4.2]) - >>> v3 = MultiVector([3, 4, 5]) - >>> v4 = MultiVector(range(10)) - >>> hash(v1), hash(v2), hash(v3), hash(v4) - (7, 384307168202284039, 2, 1) - >>> len(set([v1, v2, v3, v4])) - 4 - - -""" - -from array import array -import math -import functools -import operator - - -class MultiVector: - typecode = 'd' - - def __init__(self, components): - self._components = array(self.typecode, components) - - def __iter__(self): - return iter(self._components) - - def __repr__(self): - components = ', '.join(repr(x) for x in self) - return 'MultiVector([{}])'.format(components) - - def __str__(self): - return str(tuple(self)) - - def __bytes__(self): - return bytes(self._components) - - def __eq__(self, other): - return tuple(self) == tuple(other) - - def __hash__(self): - hashes = (hash(x) for x in self) - return functools.reduce(operator.xor, hashes) - - def __abs__(self): - return math.sqrt(sum(x * x for x in self)) - - def __bool__(self): - return bool(abs(self)) - - @classmethod - def frombytes(cls, octets): - memv = memoryview(octets).cast(cls.typecode) - return cls(memv) diff --git a/classes/multivector_v2.py b/classes/multivector_v2.py deleted file mode 100644 index e46ef9f..0000000 --- a/classes/multivector_v2.py +++ /dev/null @@ -1,164 +0,0 @@ -""" -A multi-dimensional ``MultiVector`` class, take 2 - -A ``MultiVector`` is built from an iterable of numbers:: - - >>> MultiVector([3.1, 4.2]) - MultiVector([3.1, 4.2]) - >>> MultiVector((3, 4, 5)) - MultiVector([3.0, 4.0, 5.0]) - >>> MultiVector(range(10)) - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - - -Tests with 2-dimensions (same results as ``vector_v1.py``):: - - >>> v1 = MultiVector([3, 4]) - >>> x, y = v1 - >>> x, y - (3.0, 4.0) - >>> v1 - MultiVector([3.0, 4.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0) - >>> octets = bytes(v1) - >>> octets - b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' - >>> abs(v1) - 5.0 - >>> bool(v1), bool(MultiVector([0, 0])) - (True, False) - - -Test of ``.frombytes()`` class method: - - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0]) - >>> v1 == v1_clone - True - - -Tests with 3-dimensions:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> x, y, z = v1 - >>> x, y, z - (3.0, 4.0, 5.0) - >>> v1 - MultiVector([3.0, 4.0, 5.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0, 5.0) - >>> abs(v1) # doctest:+ELLIPSIS - 7.071067811... - >>> bool(v1), bool(MultiVector([0, 0, 0])) - (True, False) - - -Tests with many dimensions:: - - >>> v7 = MultiVector(range(7)) - >>> v7 - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - >>> abs(v7) # doctest:+ELLIPSIS - 9.53939201... - - -Test of ``.__bytes__`` and ``.frombytes()`` methods:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0, 5.0]) - >>> v1 == v1_clone - True - - -Tests of sequence behavior:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> len(v1) - 3 - >>> v1[0], v1[len(v1)-1], v1[-1] - (3.0, 5.0, 5.0) - - -Test of slicing:: - -# BEGIN MULTIVECTOR_V2_DEMO - - >>> v7 = MultiVector(range(7)) - >>> v7[-1] # <1> - 6.0 - >>> v7[1:4] # <2> - MultiVector([1.0, 2.0, 3.0]) - >>> v7[-1:] # <3> - MultiVector([6.0]) - >>> v7[1,2] # <4> - Traceback (most recent call last): - ... - TypeError: MultiVector indices must be integers - -# END MULTIVECTOR_V2_DEMO - -""" - -from array import array -import reprlib -import math - - -class MultiVector: - typecode = 'd' - - def __init__(self, components): - self._components = array(self.typecode, components) - - def __iter__(self): - return iter(self._components) - - def __repr__(self): - components = reprlib.repr(self._components) - components = components[components.find('['):-1] - return 'MultiVector({})'.format(components) - - def __str__(self): - return str(tuple(self)) - - def __bytes__(self): - return bytes(self._components) - - def __eq__(self, other): - return tuple(self) == tuple(other) - - def __abs__(self): - return math.sqrt(sum(x * x for x in self)) - - def __bool__(self): - return bool(abs(self)) - -# BEGIN MULTIVECTOR_V2 - def __len__(self): - return len(self._components) - - def __getitem__(self, index): - cls = type(self) # <1> - if isinstance(index, slice): - return cls(self._components[index]) # <2> - elif isinstance(index, int): - return self._components[index] # <3> - else: - msg = '{cls.__name__} indices must be integers' - raise TypeError(msg.format(cls=cls)) # <4> -# END MULTIVECTOR_V2 - - @classmethod - def frombytes(cls, octets): - memv = memoryview(octets).cast(cls.typecode) - return cls(memv) diff --git a/classes/multivector_v3.py b/classes/multivector_v3.py deleted file mode 100644 index b8df1cc..0000000 --- a/classes/multivector_v3.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -A multi-dimensional ``MultiVector`` class, take 2 - -A ``MultiVector`` is built from an iterable of numbers:: - - >>> MultiVector([3.1, 4.2]) - MultiVector([3.1, 4.2]) - >>> MultiVector((3, 4, 5)) - MultiVector([3.0, 4.0, 5.0]) - >>> MultiVector(range(10)) - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - - -Tests with 2-dimensions (same results as ``vector_v1.py``):: - - >>> v1 = MultiVector([3, 4]) - >>> x, y = v1 - >>> x, y - (3.0, 4.0) - >>> v1 - MultiVector([3.0, 4.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0) - >>> octets = bytes(v1) - >>> octets - b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' - >>> abs(v1) - 5.0 - >>> bool(v1), bool(MultiVector([0, 0])) - (True, False) - - -Test of ``.frombytes()`` class method: - - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0]) - >>> v1 == v1_clone - True - - -Tests with 3-dimensions:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> x, y, z = v1 - >>> x, y, z - (3.0, 4.0, 5.0) - >>> v1 - MultiVector([3.0, 4.0, 5.0]) - >>> v1_clone = eval(repr(v1)) - >>> v1 == v1_clone - True - >>> print(v1) - (3.0, 4.0, 5.0) - >>> abs(v1) # doctest:+ELLIPSIS - 7.071067811... - >>> bool(v1), bool(MultiVector([0, 0, 0])) - (True, False) - - -Tests with many dimensions:: - - >>> v7 = MultiVector(range(7)) - >>> v7 - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) - >>> abs(v7) # doctest:+ELLIPSIS - 9.53939201... - - -Test of ``.__bytes__`` and ``.frombytes()`` methods:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> v1_clone = MultiVector.frombytes(bytes(v1)) - >>> v1_clone - MultiVector([3.0, 4.0, 5.0]) - >>> v1 == v1_clone - True - - -Tests of sequence behavior:: - - >>> v1 = MultiVector([3, 4, 5]) - >>> len(v1) - 3 - >>> v1[0], v1[len(v1)-1], v1[-1] - (3.0, 5.0, 5.0) - - -Test of slicing:: - - >>> v7 = MultiVector(range(7)) - >>> v7[-1] - 6.0 - >>> v7[1:4] - MultiVector([1.0, 2.0, 3.0]) - >>> v7[-1:] - MultiVector([6.0]) - >>> v7[1,2] - Traceback (most recent call last): - ... - TypeError: MultiVector indices must be integers - - -Tests of dynamic attribute access:: - - >>> v7 = MultiVector(range(10)) - >>> v7.x - 0.0 - >>> v7.y, v7.z, v7.t, v7.u, v7.v, v7.w - (1.0, 2.0, 3.0, 4.0, 5.0, 6.0) - -Dynamic attribute lookup failures:: - - >>> v7.k - Traceback (most recent call last): - ... - AttributeError: 'MultiVector' object has no attribute 'k' - >>> v3 = MultiVector(range(3)) - >>> v3.t - Traceback (most recent call last): - ... - AttributeError: 'MultiVector' object has no attribute 't' - >>> v3.spam - Traceback (most recent call last): - ... - AttributeError: 'MultiVector' object has no attribute 'spam' - - -""" - -from array import array -import reprlib -import math - - -class MultiVector: - typecode = 'd' - - def __init__(self, components): - self._components = array(self.typecode, components) - - def __iter__(self): - return iter(self._components) - - def __repr__(self): - components = reprlib.repr(self._components) - components = components[components.find('['):-1] - return 'MultiVector({})'.format(components) - - def __str__(self): - return str(tuple(self)) - - def __bytes__(self): - return bytes(self._components) - - def __eq__(self, other): - return tuple(self) == tuple(other) - - def __abs__(self): - return math.sqrt(sum(x * x for x in self)) - - def __bool__(self): - return bool(abs(self)) - - def __len__(self): - return len(self._components) - - def __getitem__(self, index): - cls = type(self) - if isinstance(index, slice): - return cls(self._components[index]) - elif isinstance(index, int): - return self._components[index] - else: - msg = '{.__name__} indices must be integers' - raise TypeError(msg.format(cls)) - -# BEGIN MULTIVECTOR_V3 - shortcut_names = 'xyztuvw' - - def __getattr__(self, name): - cls = type(self) # <1> - if len(name) == 1: # <2> - pos = cls.shortcut_names.find(name) # <3> - if 0 <= pos < len(self._components): # <4> - return self._components[pos] - msg = '{.__name__!r} object has no attribute {!r}' # <5> - raise AttributeError(msg.format(cls, name)) - -# END MULTIVECTOR_V3 - - @classmethod - def frombytes(cls, octets): - memv = memoryview(octets).cast(cls.typecode) - return cls(memv) diff --git a/classes/spherical-coordinates.txt b/classes/spherical-coordinates.txt new file mode 100644 index 0000000..c92486c --- /dev/null +++ b/classes/spherical-coordinates.txt @@ -0,0 +1,48 @@ +# Test data for spherical coordinates computed by Vector.angles() +# +# π π/2 π/3 π/4 +# 3.141592653590 1.570796326795 1.047197551197 0.785398163397 +# +# azimuth +# x y θ +# x1 x2 r Φ1 + 1 1 1.414213562373 0.785398163397 + 1 0 1.000000000000 0.000000000000 + 0 1 1.000000000000 1.570796326795 + 0 0 0.000000000000 0.000000000000 + 1 -1 1.414213562373 5.497787143782 + -1 1 1.414213562373 2.356194490192 + 0 -1 1.000000000000 4.712388980385 + -1 -1 1.414213562373 3.926990816987 +# +# x y z θ Φ +# x1 x2 x3 r Φ1 Φ2 + 1 1 1 1.732050807569 0.955316618125 0.785398163397 + 2 2 2 3.464101615138 0.955316618125 0.785398163397 + 0 0 0 0.000000000000 0.000000000000 0.000000000000 + 1 0 0 1.000000000000 0.000000000000 0.000000000000 + 0 1 0 1.000000000000 1.570796326795 0.000000000000 + 0 0 1 1.000000000000 1.570796326795 1.570796326795 + 1 1 0 1.414213562373 0.785398163397 0.000000000000 + 1 0 1 1.414213562373 0.785398163397 1.570796326795 + 0 1 1 1.414213562373 1.570796326795 0.785398163397 + 1 1 -1 1.732050807569 0.955316618125 5.497787143782 +# +# x y z t θ Φ +# x1 x2 x3 x4 r Φ1 Φ2 Φ3 + 1 1 1 0 1.732050807569 0.955316618125 0.785398163397 0.000000000000 + 2 2 2 0 3.464101615138 0.955316618125 0.785398163397 0.000000000000 + 1 1 1 1 2.000000000000 1.047197551197 0.955316618125 0.785398163397 + 2 2 2 2 4.000000000000 1.047197551197 0.955316618125 0.785398163397 + 1 0 0 0 1.000000000000 0.000000000000 0.000000000000 0.000000000000 + 0 1 0 0 1.000000000000 1.570796326795 0.000000000000 0.000000000000 + 0 0 1 0 1.000000000000 1.570796326795 1.570796326795 0.000000000000 + 0 0 0 1 1.000000000000 1.570796326795 1.570796326795 1.570796326795 + 1 1 0 0 1.414213562373 0.785398163397 0.000000000000 0.000000000000 + 0 1 1 0 1.414213562373 1.570796326795 0.785398163397 0.000000000000 + 0 0 1 1 1.414213562373 1.570796326795 1.570796326795 0.785398163397 + 1 0 0 1 1.414213562373 0.785398163397 1.570796326795 1.570796326795 + 1 0 1 0 1.414213562373 0.785398163397 1.570796326795 0.000000000000 + 0 1 0 1 1.414213562373 1.570796326795 0.785398163397 1.570796326795 + 1 1 1 -1 2.000000000000 1.047197551197 0.955316618125 5.497787143782 + -1 -1 -1 -1 2.000000000000 2.094395102393 2.186276035465 3.926990816987 diff --git a/classes/test_vector_spherical.py b/classes/test_vector_spherical.py new file mode 100644 index 0000000..1f99a8a --- /dev/null +++ b/classes/test_vector_spherical.py @@ -0,0 +1,37 @@ +""" +Test spherical coordinates in ``Vector`` class +""" + +import sys +from vector_v5 import Vector + +FIXTURE = 'spherical-coordinates.txt' +EPSILON = 10**-8 + +def parse_float_cells(cells): + floats = [] + for cell in cells: + try: + floats.append(float(cell)) + except ValueError: + continue + return floats + +def load_fixture(verbose=False): + with open(FIXTURE, encoding='utf8') as text: + for line in text: + if line.startswith('#'): # comment line + continue + cells = line.split('\t') + cartesian = parse_float_cells(cells[:5]) + spherical = parse_float_cells(cells[5:]) + v = Vector(cartesian) + if verbose: + print(repr(v), '\t->', spherical) + diff = abs(abs(v) - spherical[0]) + assert diff < EPSILON, 'expected {}, got {}'.format(spherical[0], abs(v)) + assert all(abs(av - af) < EPSILON for av, af in zip(v.angles(), spherical[1:])), ( + 'expected {}, got {}'.format(spherical[1:], list(v.angles()))) + +if __name__=='__main__': + load_fixture('-v' in sys.argv) diff --git a/classes/vector_v0.py b/classes/vector2d_v0.py similarity index 75% rename from classes/vector_v0.py rename to classes/vector2d_v0.py index b35fbbf..ce21628 100644 --- a/classes/vector_v0.py +++ b/classes/vector2d_v0.py @@ -1,14 +1,14 @@ """ A 2-dimensional vector class -# BEGIN VECTOR_V0_DEMO +# BEGIN VECTOR2D_V0_DEMO - >>> v1 = Vector(3, 4) + >>> v1 = Vector2d(3, 4) >>> x, y = v1 #<1> >>> x, y (3.0, 4.0) >>> v1 #<2> - Vector(3.0, 4.0) + Vector2d(3.0, 4.0) >>> v1_clone = eval(repr(v1)) #<3> >>> v1 == v1_clone True @@ -19,18 +19,18 @@ A 2-dimensional vector class b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' >>> abs(v1) #<6> 5.0 - >>> bool(v1), bool(Vector(0, 0)) #<7> + >>> bool(v1), bool(Vector2d(0, 0)) #<7> (True, False) -# END VECTOR_V0_DEMO +# END VECTOR2D_V0_DEMO """ -# BEGIN VECTOR_V0 +# BEGIN VECTOR2D_V0 from array import array import math -class Vector: +class Vector2d: typecode = 'd' # <1> def __init__(self, x, y): @@ -41,13 +41,13 @@ class Vector: return (i for i in (self.x, self.y)) # <3> def __repr__(self): - return 'Vector({!r}, {!r})'.format(*self) # <4> + return 'Vector2d({!r}, {!r})'.format(*self) # <4> def __str__(self): return str(tuple(self)) # <5> def __bytes__(self): - return bytes(array(Vector.typecode, self)) # <6> + return bytes(array(Vector2d.typecode, self)) # <6> def __eq__(self, other): return tuple(self) == tuple(other) # <7> @@ -57,4 +57,4 @@ class Vector: def __bool__(self): return bool(abs(self)) # <8> -# END VECTOR_V0 +# END VECTOR2D_V0 diff --git a/classes/vector2d_v1.py b/classes/vector2d_v1.py new file mode 100644 index 0000000..ac4adfe --- /dev/null +++ b/classes/vector2d_v1.py @@ -0,0 +1,85 @@ +""" +A 2-dimensional vector class + + >>> v1 = Vector2d(3, 4) + >>> x, y = v1 #<1> + >>> x, y + (3.0, 4.0) + >>> v1 #<2> + Vector2d(3.0, 4.0) + >>> v1_clone = eval(repr(v1)) #<3> + >>> v1 == v1_clone + True + >>> print(v1) #<4> + (3.0, 4.0) + >>> octets = bytes(v1) #<5> + >>> octets + b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) #<6> + 5.0 + >>> bool(v1), bool(Vector2d(0, 0)) #<7> + (True, False) + +Test of .frombytes() class method: + + >>> v1_clone = Vector2d.frombytes(bytes(v1)) + >>> v1_clone + Vector2d(3.0, 4.0) + >>> v1 == v1_clone + True + +So far, Vector2d instances are unhashable: + +# BEGIN VECTOR2D_V1_UNHASHABLE_DEMO + >>> v1 = Vector2d(3, 4) + >>> hash(v1) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'Vector2d' + >>> set([v1]) + Traceback (most recent call last): + ... + TypeError: unhashable type: 'Vector2d' + +# END VECTOR2D_V1_UNHASHABLE_DEMO + +""" + +from array import array +import math + + +class Vector2d: + typecode = 'd' + + def __init__(self, x, y): + self.x = float(x) + self.y = float(y) + + def __iter__(self): + return (i for i in (self.x, self.y)) + + def __repr__(self): + return 'Vector2d({!r}, {!r})'.format(*self) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return bytes(array(Vector2d.typecode, self)) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __abs__(self): + return math.hypot(self.x, self.y) + + def __bool__(self): + return bool(abs(self)) + +# BEGIN VECTOR2D_V1 + @classmethod # <1> + def frombytes(cls, octets): # <2> + memv = memoryview(octets).cast(cls.typecode) # <3> + return cls(*memv) # <4> +# END VECTOR2D_V1 diff --git a/classes/vector2d_v2.py b/classes/vector2d_v2.py new file mode 100644 index 0000000..6233a0d --- /dev/null +++ b/classes/vector2d_v2.py @@ -0,0 +1,118 @@ +""" +A 2-dimensional vector class + + >>> v1 = Vector2d(3, 4) + >>> x, y = v1 + >>> x, y + (3.0, 4.0) + >>> v1 + Vector2d(3.0, 4.0) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0) + >>> octets = bytes(v1) + >>> octets + b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) + 5.0 + >>> bool(v1), bool(Vector2d(0, 0)) + (True, False) + + +Test of ``.frombytes()`` class method: + + >>> v1_clone = Vector2d.frombytes(bytes(v1)) + >>> v1_clone + Vector2d(3.0, 4.0) + >>> v1 == v1_clone + True + + +Tests of ``format()`` with Cartesian coordinates: + + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of the ``angle`` method:: + + >>> Vector2d(0, 0).angle() + 0.0 + >>> Vector2d(1, 0).angle() + 0.0 + >>> epsilon = 10**-8 + >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon + True + >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon + True + + +Tests of ``format()`` with polar coordinates: + + >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector2d(1, 1), '.3ep') + '<1.414e+00, 7.854e-01>' + >>> format(Vector2d(1, 1), '0.5fp') + '<1.41421, 0.78540>' + +""" + +# BEGIN VECTOR2D_V2 +from array import array +import math + + +class Vector2d: + typecode = 'd' + + def __init__(self, x, y): + self.x = float(x) + self.y = float(y) + + def __iter__(self): + return (i for i in (self.x, self.y)) + + def __repr__(self): + return 'Vector2d({!r}, {!r})'.format(*self) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return bytes(array(Vector2d.typecode, self)) + + def __eq__(self, other): + return tuple(self) == tuple(other) + + def __abs__(self): + return math.hypot(self.x, self.y) + + def __bool__(self): + return bool(abs(self)) + + def angle(self): + return math.atan2(self.y, self.x) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('p'): + fmt_spec = fmt_spec[:-1] + coords = (abs(self), self.angle()) + outer_fmt = '<{}, {}>' + else: + coords = self + outer_fmt = '({}, {})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(*components) + + @classmethod + def frombytes(cls, octets): + memv = memoryview(octets).cast(cls.typecode) + return cls(*memv) +# END VECTOR2D_V2 diff --git a/classes/vector_v2_fmt_snippet.py b/classes/vector2d_v2_fmt_snippet.py similarity index 75% rename from classes/vector_v2_fmt_snippet.py rename to classes/vector2d_v2_fmt_snippet.py index 545a723..ff895cb 100644 --- a/classes/vector_v2_fmt_snippet.py +++ b/classes/vector2d_v2_fmt_snippet.py @@ -1,12 +1,12 @@ """ A 2-dimensional vector class - >>> v1 = Vector(3, 4) + >>> v1 = Vector2d(3, 4) >>> x, y = v1 >>> x, y (3.0, 4.0) >>> v1 - Vector(3.0, 4.0) + Vector2d(3.0, 4.0) >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True @@ -17,15 +17,15 @@ A 2-dimensional vector class b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' >>> abs(v1) 5.0 - >>> bool(v1), bool(Vector(0, 0)) + >>> bool(v1), bool(Vector2d(0, 0)) (True, False) Test of ``.frombytes()`` class method: - >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone - Vector(3.0, 4.0) + Vector2d(3.0, 4.0) >>> v1 == v1_clone True @@ -42,24 +42,24 @@ Tests of ``format()`` with Cartesian coordinates: Tests of the ``angle`` method:: - >>> Vector(0, 0).angle() + >>> Vector2d(0, 0).angle() 0.0 - >>> Vector(1, 0).angle() + >>> Vector2d(1, 0).angle() 0.0 >>> epsilon = 10**-8 - >>> abs(Vector(0, 1).angle() - math.pi/2) < epsilon + >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon True - >>> abs(Vector(1, 1).angle() - math.pi/4) < epsilon + >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon True Tests of ``format()`` with polar coordinates: - >>> format(Vector(1, 1), 'p') # doctest:+ELLIPSIS + >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' - >>> format(Vector(1, 1), '.3ep') + >>> format(Vector2d(1, 1), '.3ep') '<1.414e+00, 7.854e-01>' - >>> format(Vector(1, 1), '0.5fp') + >>> format(Vector2d(1, 1), '0.5fp') '<1.41421, 0.78540>' """ @@ -68,7 +68,7 @@ from array import array import math -class Vector: +class Vector2d: typecode = 'd' def __init__(self, x, y): @@ -79,13 +79,13 @@ class Vector: return (i for i in (self.x, self.y)) def __repr__(self): - return 'Vector({!r}, {!r})'.format(*self) + return 'Vector2d({!r}, {!r})'.format(*self) def __str__(self): return str(tuple(self)) def __bytes__(self): - return bytes(array(Vector.typecode, self)) + return bytes(array(Vector2d.typecode, self)) def __eq__(self, other): return tuple(self) == tuple(other) @@ -99,7 +99,7 @@ class Vector: def angle(self): return math.atan2(self.y, self.x) -# BEGIN VECTOR_V2_FORMAT +# BEGIN VECTOR2D_V2_FORMAT def __format__(self, fmt_spec=''): if fmt_spec.endswith('p'): # <1> fmt_spec = fmt_spec[:-1] # <2> @@ -110,7 +110,7 @@ class Vector: outer_fmt = '({}, {})' # <6> components = (format(c, fmt_spec) for c in coords) # <7> return outer_fmt.format(*components) # <8> -# END VECTOR_V2_FORMAT +# END VECTOR2D_V2_FORMAT @classmethod def frombytes(cls, octets): diff --git a/classes/vector2d_v3.py b/classes/vector2d_v3.py new file mode 100644 index 0000000..66b09ed --- /dev/null +++ b/classes/vector2d_v3.py @@ -0,0 +1,156 @@ +""" +A 2-dimensional vector class + + >>> v1 = Vector2d(3, 4) + >>> x, y = v1 #<1> + >>> x, y + (3.0, 4.0) + >>> v1 #<2> + Vector2d(3.0, 4.0) + >>> v1_clone = eval(repr(v1)) #<3> + >>> v1 == v1_clone + True + >>> print(v1) #<4> + (3.0, 4.0) + >>> octets = bytes(v1) #<5> + >>> octets + b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' + >>> abs(v1) #<6> + 5.0 + >>> bool(v1), bool(Vector2d(0, 0)) #<7> + (True, False) + + +Test of .frombytes() class method: + + >>> v1_clone = Vector2d.frombytes(bytes(v1)) + >>> v1_clone + Vector2d(3.0, 4.0) + >>> v1 == v1_clone + True + + +Tests of ``format()`` with Cartesian coordinates: + + >>> format(v1) + '(3.0, 4.0)' + >>> format(v1, '.2f') + '(3.00, 4.00)' + >>> format(v1, '.3e') + '(3.000e+00, 4.000e+00)' + + +Tests of the ``angle`` method:: + + >>> Vector2d(0, 0).angle() + 0.0 + >>> Vector2d(1, 0).angle() + 0.0 + >>> epsilon = 10**-8 + >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon + True + >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon + True + + +Tests of ``format()`` with polar coordinates: + + >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS + '<1.414213..., 0.785398...>' + >>> format(Vector2d(1, 1), '.3ep') + '<1.414e+00, 7.854e-01>' + >>> format(Vector2d(1, 1), '0.5fp') + '<1.41421, 0.78540>' + +# BEGIN VECTOR2D_V3_DEMO +Test of `x` and `y` read-only properties: + + >>> v1.x, v1.y + (3.0, 4.0) + >>> v1.x = 123 + Traceback (most recent call last): + ... + AttributeError: can't set attribute + +# END VECTOR2D_V3_HASH_DEMO + +# BEGIN VECTOR2D_V3_HASH_DEMO + + >>> v1 = Vector2d(3, 4) + >>> v2 = Vector2d(3.1, 4.2) + >>> hash(v1), hash(v2) + (7, 384307168202284039) + >>> len(set([v1, v2])) + 2 + +# END VECTOR2D_V3_DEMO + + +""" + +from array import array +import math + +# BEGIN VECTOR2D_V3 +class Vector2d: + typecode = 'd' + + def __init__(self, x, y): + self.__x = float(x) # <1> + self.__y = float(y) + + @property # <2> + def x(self): # <3> + return self.__x # <4> + + @property # <5> + def y(self): + return self.__y + + def __iter__(self): + return (i for i in (self.x, self.y)) # <6> + + # remaining methods follow (omitted in book listing) +# END VECTOR2D_V3 + + def __repr__(self): + return 'Vector2d({!r}, {!r})'.format(*self) + + def __str__(self): + return str(tuple(self)) + + def __bytes__(self): + return bytes(array(Vector2d.typecode, self)) + + def __eq__(self, other): + return tuple(self) == tuple(other) + +# BEGIN VECTOR_V3_HASH + def __hash__(self): + return hash(self.x) ^ hash(self.y) +# END VECTOR_V3_HASH + + def __abs__(self): + return math.hypot(self.x, self.y) + + def __bool__(self): + return bool(abs(self)) + + def angle(self): + return math.atan2(self.y, self.x) + + def __format__(self, fmt_spec=''): + if fmt_spec.endswith('p'): + fmt_spec = fmt_spec[:-1] + coords = (abs(self), self.angle()) + outer_fmt = '<{}, {}>' + else: + coords = self + outer_fmt = '({}, {})' + components = (format(c, fmt_spec) for c in coords) + return outer_fmt.format(*components) + + @classmethod + def frombytes(cls, octets): + memv = memoryview(octets).cast(cls.typecode) + return cls(*memv) diff --git a/classes/vector_v3_slots.py b/classes/vector2d_v3_slots.py similarity index 73% rename from classes/vector_v3_slots.py rename to classes/vector2d_v3_slots.py index 3337656..1a6d21d 100644 --- a/classes/vector_v3_slots.py +++ b/classes/vector2d_v3_slots.py @@ -1,12 +1,12 @@ """ A 2-dimensional vector class - >>> v1 = Vector(3, 4) + >>> v1 = Vector2d(3, 4) >>> x, y = v1 #<1> >>> x, y (3.0, 4.0) >>> v1 #<2> - Vector(3.0, 4.0) + Vector2d(3.0, 4.0) >>> v1_clone = eval(repr(v1)) #<3> >>> v1 == v1_clone True @@ -17,15 +17,15 @@ A 2-dimensional vector class b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' >>> abs(v1) #<6> 5.0 - >>> bool(v1), bool(Vector(0, 0)) #<7> + >>> bool(v1), bool(Vector2d(0, 0)) #<7> (True, False) Test of .frombytes() class method: - >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone = Vector2d.frombytes(bytes(v1)) >>> v1_clone - Vector(3.0, 4.0) + Vector2d(3.0, 4.0) >>> v1 == v1_clone True @@ -42,27 +42,27 @@ Tests of ``format()`` with Cartesian coordinates: Tests of the ``angle`` method:: - >>> Vector(0, 0).angle() + >>> Vector2d(0, 0).angle() 0.0 - >>> Vector(1, 0).angle() + >>> Vector2d(1, 0).angle() 0.0 >>> epsilon = 10**-8 - >>> abs(Vector(0, 1).angle() - math.pi/2) < epsilon + >>> abs(Vector2d(0, 1).angle() - math.pi/2) < epsilon True - >>> abs(Vector(1, 1).angle() - math.pi/4) < epsilon + >>> abs(Vector2d(1, 1).angle() - math.pi/4) < epsilon True Tests of ``format()`` with polar coordinates: - >>> format(Vector(1, 1), 'p') # doctest:+ELLIPSIS + >>> format(Vector2d(1, 1), 'p') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' - >>> format(Vector(1, 1), '.3ep') + >>> format(Vector2d(1, 1), '.3ep') '<1.414e+00, 7.854e-01>' - >>> format(Vector(1, 1), '0.5fp') + >>> format(Vector2d(1, 1), '0.5fp') '<1.41421, 0.78540>' -# BEGIN VECTOR_V3_DEMO +# BEGIN VECTOR2D_V3_DEMO Test of `x` and `y` read-only properties: >>> v1.x, v1.y @@ -72,18 +72,18 @@ Test of `x` and `y` read-only properties: ... AttributeError: can't set attribute -# END VECTOR_V3_HASH_DEMO +# END VECTOR2D_V3_HASH_DEMO -# BEGIN VECTOR_V3_HASH_DEMO +# BEGIN VECTOR2D_V3_HASH_DEMO - >>> v1 = Vector(3, 4) - >>> v2 = Vector(3.1, 4.2) + >>> v1 = Vector2d(3, 4) + >>> v2 = Vector2d(3.1, 4.2) >>> hash(v1), hash(v2) (7, 384307168202284039) >>> len(set([v1, v2])) 2 -# END VECTOR_V3_DEMO +# END VECTOR2D_V3_DEMO """ @@ -91,14 +91,14 @@ Test of `x` and `y` read-only properties: from array import array import math -# BEGIN VECTOR_V3_SLOTS -class Vector: +# BEGIN VECTOR2D_V3_SLOTS +class Vector2d: __slots__ = ('__x', '__y') typecode = 'd' # methods follow (omitted in book listing) -# END VECTOR_V3_SLOTS +# END VECTOR2D_V3_SLOTS def __init__(self, x, y): self.__x = float(x) @@ -113,24 +113,24 @@ class Vector: return self.__y def __iter__(self): - return (i for i in (self.x, self.y)) # <6> + return (i for i in (self.x, self.y)) def __repr__(self): - return 'Vector({!r}, {!r})'.format(*self) + return 'Vector2d({!r}, {!r})'.format(*self) def __str__(self): return str(tuple(self)) def __bytes__(self): - return bytes(array(Vector.typecode, self)) + return bytes(array(Vector2d.typecode, self)) def __eq__(self, other): return tuple(self) == tuple(other) -# BEGIN VECTOR_V3_HASH +# BEGIN VECTOR2D_V3_HASH def __hash__(self): return hash(self.x) ^ hash(self.y) -# END VECTOR_V3_HASH +# END VECTOR2D_V3_HASH def __abs__(self): return math.hypot(self.x, self.y) diff --git a/classes/vector_v1.py b/classes/vector_v1.py index 6a85571..1fa18a1 100644 --- a/classes/vector_v1.py +++ b/classes/vector_v1.py @@ -1,85 +1,124 @@ """ -A 2-dimensional vector class +A multi-dimensional ``Vector`` class, take 1 - >>> v1 = Vector(3, 4) - >>> x, y = v1 #<1> +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 >>> x, y (3.0, 4.0) - >>> v1 #<2> - Vector(3.0, 4.0) - >>> v1_clone = eval(repr(v1)) #<3> + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True - >>> print(v1) #<4> + >>> print(v1) (3.0, 4.0) - >>> octets = bytes(v1) #<5> + >>> octets = bytes(v1) >>> octets b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' - >>> abs(v1) #<6> + >>> abs(v1) 5.0 - >>> bool(v1), bool(Vector(0, 0)) #<7> + >>> bool(v1), bool(Vector([0, 0])) (True, False) -Test of .frombytes() class method: + +Test of ``.frombytes()`` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone - Vector(3.0, 4.0) + Vector([3.0, 4.0]) >>> v1 == v1_clone True -So far, Vector instances are unhashable: -# BEGIN VECTOR_V1_UNHASHABLE_DEMO - >>> v1 = Vector(3, 4) - >>> hash(v1) - Traceback (most recent call last): - ... - TypeError: unhashable type: 'Vector' - >>> set([v1]) - Traceback (most recent call last): - ... - TypeError: unhashable type: 'Vector' +Tests with 3-dimensions:: + + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone + True + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone + True -# END VECTOR_V1_UNHASHABLE_DEMO """ +# BEGIN VECTOR_V1 from array import array +import reprlib import math class Vector: typecode = 'd' - def __init__(self, x, y): - self.x = float(x) - self.y = float(y) + def __init__(self, components): + self._components = array(self.typecode, components) # <1> def __iter__(self): - return (i for i in (self.x, self.y)) + return iter(self._components) # <2> def __repr__(self): - return 'Vector({!r}, {!r})'.format(*self) + components = reprlib.repr(self._components) # <3> + components = components[components.find('['):-1] # <4> + return 'Vector({})'.format(components) def __str__(self): return str(tuple(self)) def __bytes__(self): - return bytes(array(Vector.typecode, self)) + return bytes(self._components) # <5> def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): - return math.hypot(self.x, self.y) + return math.sqrt(sum(x * x for x in self)) # <6> def __bool__(self): return bool(abs(self)) -# BEGIN VECTOR_V1 - @classmethod # <1> - def frombytes(cls, octets): # <2> - memv = memoryview(octets).cast(cls.typecode) # <3> - return cls(*memv) # <4> + @classmethod + def frombytes(cls, octets): + memv = memoryview(octets).cast(cls.typecode) + return cls(memv) # <7> # END VECTOR_V1 diff --git a/classes/vector_v2.py b/classes/vector_v2.py index c63e6e7..e37241c 100644 --- a/classes/vector_v2.py +++ b/classes/vector_v2.py @@ -1,12 +1,24 @@ """ -A 2-dimensional vector class +A multi-dimensional ``Vector`` class, take 2 - >>> v1 = Vector(3, 4) +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) >>> x, y = v1 >>> x, y (3.0, 4.0) >>> v1 - Vector(3.0, 4.0) + Vector([3.0, 4.0]) >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True @@ -17,98 +29,136 @@ A 2-dimensional vector class b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' >>> abs(v1) 5.0 - >>> bool(v1), bool(Vector(0, 0)) + >>> bool(v1), bool(Vector([0, 0])) (True, False) + Test of ``.frombytes()`` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone - Vector(3.0, 4.0) + Vector([3.0, 4.0]) >>> v1 == v1_clone True -Tests of ``format()`` with Cartesian coordinates: - >>> format(v1) - '(3.0, 4.0)' - >>> format(v1, '.2f') - '(3.00, 4.00)' - >>> format(v1, '.3e') - '(3.000e+00, 4.000e+00)' +Tests with 3-dimensions:: - -Tests of the ``angle`` method:: - - >>> Vector(0, 0).angle() - 0.0 - >>> Vector(1, 0).angle() - 0.0 - >>> epsilon = 10**-8 - >>> abs(Vector(0, 1).angle() - math.pi/2) < epsilon + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone True - >>> abs(Vector(1, 1).angle() - math.pi/4) < epsilon + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone True -Tests of ``format()`` with polar coordinates: +Tests of sequence behavior:: - >>> format(Vector(1, 1), 'p') # doctest:+ELLIPSIS - '<1.414213..., 0.785398...>' - >>> format(Vector(1, 1), '.3ep') - '<1.414e+00, 7.854e-01>' - >>> format(Vector(1, 1), '0.5fp') - '<1.41421, 0.78540>' + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) + + +Test of slicing:: + +# BEGIN VECTOR_V2_DEMO + + >>> v7 = Vector(range(7)) + >>> v7[-1] # <1> + 6.0 + >>> v7[1:4] # <2> + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] # <3> + Vector([6.0]) + >>> v7[1,2] # <4> + Traceback (most recent call last): + ... + TypeError: Vector indices must be integers + +# END VECTOR_V2_DEMO """ from array import array +import reprlib import math class Vector: typecode = 'd' - def __init__(self, x, y): - self.x = float(x) - self.y = float(y) + def __init__(self, components): + self._components = array(self.typecode, components) def __iter__(self): - return (i for i in (self.x, self.y)) + return iter(self._components) def __repr__(self): - return 'Vector({!r}, {!r})'.format(*self) + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) def __str__(self): return str(tuple(self)) def __bytes__(self): - return bytes(array(Vector.typecode, self)) + return bytes(self._components) def __eq__(self, other): return tuple(self) == tuple(other) def __abs__(self): - return math.hypot(self.x, self.y) + return math.sqrt(sum(x * x for x in self)) def __bool__(self): return bool(abs(self)) - def angle(self): - return math.atan2(self.y, self.x) +# BEGIN VECTOR_V2 + def __len__(self): + return len(self._components) - def __format__(self, fmt_spec=''): - if fmt_spec.endswith('p'): - fmt_spec = fmt_spec[:-1] - coords = (abs(self), self.angle()) - outer_fmt = '<{}, {}>' + def __getitem__(self, index): + cls = type(self) # <1> + if isinstance(index, slice): + return cls(self._components[index]) # <2> + elif isinstance(index, int): + return self._components[index] # <3> else: - coords = self - outer_fmt = '({}, {})' - components = (format(c, fmt_spec) for c in coords) - return outer_fmt.format(*components) + msg = '{cls.__name__} indices must be integers' + raise TypeError(msg.format(cls=cls)) # <4> +# END VECTOR_V2 @classmethod def frombytes(cls, octets): memv = memoryview(octets).cast(cls.typecode) - return cls(*memv) + return cls(memv) diff --git a/classes/vector_v3.py b/classes/vector_v3.py index 28b8a52..ec73877 100644 --- a/classes/vector_v3.py +++ b/classes/vector_v3.py @@ -1,155 +1,198 @@ """ -A 2-dimensional vector class +A multi-dimensional ``Vector`` class, take 3 - >>> v1 = Vector(3, 4) - >>> x, y = v1 #<1> +A ``Vector`` is built from an iterable of numbers:: + + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + + +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: + + >>> v1 = Vector([3, 4]) + >>> x, y = v1 >>> x, y (3.0, 4.0) - >>> v1 #<2> - Vector(3.0, 4.0) - >>> v1_clone = eval(repr(v1)) #<3> + >>> v1 + Vector([3.0, 4.0]) + >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True - >>> print(v1) #<4> + >>> print(v1) (3.0, 4.0) - >>> octets = bytes(v1) #<5> + >>> octets = bytes(v1) >>> octets b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' - >>> abs(v1) #<6> + >>> abs(v1) 5.0 - >>> bool(v1), bool(Vector(0, 0)) #<7> + >>> bool(v1), bool(Vector([0, 0])) (True, False) -Test of .frombytes() class method: + +Test of ``.frombytes()`` class method: >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone - Vector(3.0, 4.0) + Vector([3.0, 4.0]) >>> v1 == v1_clone True -Tests of ``format()`` with Cartesian coordinates: - >>> format(v1) - '(3.0, 4.0)' - >>> format(v1, '.2f') - '(3.00, 4.00)' - >>> format(v1, '.3e') - '(3.000e+00, 4.000e+00)' +Tests with 3-dimensions:: - -Tests of the ``angle`` method:: - - >>> Vector(0, 0).angle() - 0.0 - >>> Vector(1, 0).angle() - 0.0 - >>> epsilon = 10**-8 - >>> abs(Vector(0, 1).angle() - math.pi/2) < epsilon + >>> v1 = Vector([3, 4, 5]) + >>> x, y, z = v1 + >>> x, y, z + (3.0, 4.0, 5.0) + >>> v1 + Vector([3.0, 4.0, 5.0]) + >>> v1_clone = eval(repr(v1)) + >>> v1 == v1_clone True - >>> abs(Vector(1, 1).angle() - math.pi/4) < epsilon + >>> print(v1) + (3.0, 4.0, 5.0) + >>> abs(v1) # doctest:+ELLIPSIS + 7.071067811... + >>> bool(v1), bool(Vector([0, 0, 0])) + (True, False) + + +Tests with many dimensions:: + + >>> v7 = Vector(range(7)) + >>> v7 + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> abs(v7) # doctest:+ELLIPSIS + 9.53939201... + + +Test of ``.__bytes__`` and ``.frombytes()`` methods:: + + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) + >>> v1_clone + Vector([3.0, 4.0, 5.0]) + >>> v1 == v1_clone True -Tests of ``format()`` with polar coordinates: +Tests of sequence behavior:: - >>> format(Vector(1, 1), 'p') # doctest:+ELLIPSIS - '<1.414213..., 0.785398...>' - >>> format(Vector(1, 1), '.3ep') - '<1.414e+00, 7.854e-01>' - >>> format(Vector(1, 1), '0.5fp') - '<1.41421, 0.78540>' + >>> v1 = Vector([3, 4, 5]) + >>> len(v1) + 3 + >>> v1[0], v1[len(v1)-1], v1[-1] + (3.0, 5.0, 5.0) -# BEGIN VECTOR_V3_DEMO -Test of `x` and `y` read-only properties: - >>> v1.x, v1.y - (3.0, 4.0) - >>> v1.x = 123 +Test of slicing:: + + >>> v7 = Vector(range(7)) + >>> v7[-1] + 6.0 + >>> v7[1:4] + Vector([1.0, 2.0, 3.0]) + >>> v7[-1:] + Vector([6.0]) + >>> v7[1,2] Traceback (most recent call last): ... - AttributeError: can't set attribute - -# END VECTOR_V3_HASH_DEMO - -# BEGIN VECTOR_V3_HASH_DEMO - - >>> v1 = Vector(3, 4) - >>> v2 = Vector(3.1, 4.2) - >>> hash(v1), hash(v2) - (7, 384307168202284039) - >>> len(set([v1, v2])) - 2 + TypeError: Vector indices must be integers -# END VECTOR_V3_DEMO +Tests of dynamic attribute access:: + + >>> v7 = Vector(range(10)) + >>> v7.x + 0.0 + >>> v7.y, v7.z, v7.t, v7.u, v7.v, v7.w + (1.0, 2.0, 3.0, 4.0, 5.0, 6.0) + +Dynamic attribute lookup failures:: + + >>> v7.k + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) + >>> v3.t + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 't' + >>> v3.spam + Traceback (most recent call last): + ... + AttributeError: 'Vector' object has no attribute 'spam' """ from array import array +import reprlib import math -# BEGIN VECTOR_V3 + class Vector: typecode = 'd' - def __init__(self, x, y): - self.__x = float(x) # <1> - self.__y = float(y) - - @property # <2> - def x(self): # <3> - return self.__x # <4> - - @property # <5> - def y(self): - return self.__y + def __init__(self, components): + self._components = array(self.typecode, components) def __iter__(self): - return (i for i in (self.x, self.y)) # <6> - - # remaining methods follow (omitted in book listing) -# END VECTOR_V3 + return iter(self._components) def __repr__(self): - return 'Vector({!r}, {!r})'.format(*self) + components = reprlib.repr(self._components) + components = components[components.find('['):-1] + return 'Vector({})'.format(components) def __str__(self): return str(tuple(self)) def __bytes__(self): - return bytes(array(Vector.typecode, self)) + return bytes(self._components) def __eq__(self, other): return tuple(self) == tuple(other) -# BEGIN VECTOR_V3_HASH - def __hash__(self): - return hash(self.x) ^ hash(self.y) -# END VECTOR_V3_HASH - def __abs__(self): - return math.hypot(self.x, self.y) + return math.sqrt(sum(x * x for x in self)) def __bool__(self): return bool(abs(self)) - def angle(self): - return math.atan2(self.y, self.x) + def __len__(self): + return len(self._components) - def __format__(self, fmt_spec=''): - if fmt_spec.endswith('p'): - fmt_spec = fmt_spec[:-1] - coords = (abs(self), self.angle()) - outer_fmt = '<{}, {}>' + def __getitem__(self, index): + cls = type(self) + if isinstance(index, slice): + return cls(self._components[index]) + elif isinstance(index, int): + return self._components[index] else: - coords = self - outer_fmt = '({}, {})' - components = (format(c, fmt_spec) for c in coords) - return outer_fmt.format(*components) + msg = '{.__name__} indices must be integers' + raise TypeError(msg.format(cls)) + +# BEGIN VECTOR_V3 + shortcut_names = 'xyztuvw' + + def __getattr__(self, name): + cls = type(self) # <1> + if len(name) == 1: # <2> + pos = cls.shortcut_names.find(name) # <3> + if 0 <= pos < len(self._components): # <4> + return self._components[pos] + msg = '{.__name__!r} object has no attribute {!r}' # <5> + raise AttributeError(msg.format(cls, name)) + +# END VECTOR_V3 @classmethod def frombytes(cls, octets): memv = memoryview(octets).cast(cls.typecode) - return cls(*memv) + return cls(memv) diff --git a/classes/multivector_v4.py b/classes/vector_v4.py similarity index 69% rename from classes/multivector_v4.py rename to classes/vector_v4.py index bd7d034..b3ac972 100644 --- a/classes/multivector_v4.py +++ b/classes/vector_v4.py @@ -1,24 +1,24 @@ """ -A multi-dimensional ``MultiVector`` class, take 2 +A multi-dimensional ``Vector`` class, take 4 -A ``MultiVector`` is built from an iterable of numbers:: +A ``Vector`` is built from an iterable of numbers:: - >>> MultiVector([3.1, 4.2]) - MultiVector([3.1, 4.2]) - >>> MultiVector((3, 4, 5)) - MultiVector([3.0, 4.0, 5.0]) - >>> MultiVector(range(10)) - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) -Tests with 2-dimensions (same results as ``vector_v1.py``):: +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: - >>> v1 = MultiVector([3, 4]) + >>> v1 = Vector([3, 4]) >>> x, y = v1 >>> x, y (3.0, 4.0) >>> v1 - MultiVector([3.0, 4.0]) + Vector([3.0, 4.0]) >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True @@ -29,27 +29,27 @@ Tests with 2-dimensions (same results as ``vector_v1.py``):: b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' >>> abs(v1) 5.0 - >>> bool(v1), bool(MultiVector([0, 0])) + >>> bool(v1), bool(Vector([0, 0])) (True, False) Test of ``.frombytes()`` class method: - >>> v1_clone = MultiVector.frombytes(bytes(v1)) + >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone - MultiVector([3.0, 4.0]) + Vector([3.0, 4.0]) >>> v1 == v1_clone True Tests with 3-dimensions:: - >>> v1 = MultiVector([3, 4, 5]) + >>> v1 = Vector([3, 4, 5]) >>> x, y, z = v1 >>> x, y, z (3.0, 4.0, 5.0) >>> v1 - MultiVector([3.0, 4.0, 5.0]) + Vector([3.0, 4.0, 5.0]) >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True @@ -57,32 +57,32 @@ Tests with 3-dimensions:: (3.0, 4.0, 5.0) >>> abs(v1) # doctest:+ELLIPSIS 7.071067811... - >>> bool(v1), bool(MultiVector([0, 0, 0])) + >>> bool(v1), bool(Vector([0, 0, 0])) (True, False) Tests with many dimensions:: - >>> v7 = MultiVector(range(7)) + >>> v7 = Vector(range(7)) >>> v7 - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) >>> abs(v7) # doctest:+ELLIPSIS 9.53939201... Test of ``.__bytes__`` and ``.frombytes()`` methods:: - >>> v1 = MultiVector([3, 4, 5]) - >>> v1_clone = MultiVector.frombytes(bytes(v1)) + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone - MultiVector([3.0, 4.0, 5.0]) + Vector([3.0, 4.0, 5.0]) >>> v1 == v1_clone True Tests of sequence behavior:: - >>> v1 = MultiVector([3, 4, 5]) + >>> v1 = Vector([3, 4, 5]) >>> len(v1) 3 >>> v1[0], v1[len(v1)-1], v1[-1] @@ -91,22 +91,22 @@ Tests of sequence behavior:: Test of slicing:: - >>> v7 = MultiVector(range(7)) + >>> v7 = Vector(range(7)) >>> v7[-1] 6.0 >>> v7[1:4] - MultiVector([1.0, 2.0, 3.0]) + Vector([1.0, 2.0, 3.0]) >>> v7[-1:] - MultiVector([6.0]) + Vector([6.0]) >>> v7[1,2] Traceback (most recent call last): ... - TypeError: MultiVector indices must be integers + TypeError: Vector indices must be integers Tests of dynamic attribute access:: - >>> v7 = MultiVector(range(10)) + >>> v7 = Vector(range(10)) >>> v7.x 0.0 >>> v7.y, v7.z, v7.t, v7.u, v7.v, v7.w @@ -117,24 +117,24 @@ Dynamic attribute lookup failures:: >>> v7.k Traceback (most recent call last): ... - AttributeError: 'MultiVector' object has no attribute 'k' - >>> v3 = MultiVector(range(3)) + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) >>> v3.t Traceback (most recent call last): ... - AttributeError: 'MultiVector' object has no attribute 't' + AttributeError: 'Vector' object has no attribute 't' >>> v3.spam Traceback (most recent call last): ... - AttributeError: 'MultiVector' object has no attribute 'spam' + AttributeError: 'Vector' object has no attribute 'spam' Tests of hashing:: - >>> v1 = MultiVector([3, 4]) - >>> v2 = MultiVector([3.1, 4.2]) - >>> v3 = MultiVector([3, 4, 5]) - >>> v6 = MultiVector(range(6)) + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) >>> hash(v1), hash(v2), hash(v3), hash(v6) (7, 384307168202284039, 2, 1) >>> len(set([v1, v2, v3, v6])) @@ -150,7 +150,7 @@ import functools import operator -class MultiVector: +class Vector: typecode = 'd' def __init__(self, components): @@ -162,7 +162,7 @@ class MultiVector: def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'MultiVector({})'.format(components) + return 'Vector({})'.format(components) def __str__(self): return str(tuple(self)) diff --git a/classes/multivector_v5.py b/classes/vector_v5.py similarity index 56% rename from classes/multivector_v5.py rename to classes/vector_v5.py index fd1adff..9e332ff 100644 --- a/classes/multivector_v5.py +++ b/classes/vector_v5.py @@ -1,24 +1,25 @@ +# BEGIN VECTOR_V5 """ -A multi-dimensional ``MultiVector`` class, take 2 +A multi-dimensional ``Vector`` class, take 5 -A ``MultiVector`` is built from an iterable of numbers:: +A ``Vector`` is built from an iterable of numbers:: - >>> MultiVector([3.1, 4.2]) - MultiVector([3.1, 4.2]) - >>> MultiVector((3, 4, 5)) - MultiVector([3.0, 4.0, 5.0]) - >>> MultiVector(range(10)) - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + >>> Vector([3.1, 4.2]) + Vector([3.1, 4.2]) + >>> Vector((3, 4, 5)) + Vector([3.0, 4.0, 5.0]) + >>> Vector(range(10)) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) -Tests with 2-dimensions (same results as ``vector_v1.py``):: +Tests with 2-dimensions (same results as ``vector2d_v1.py``):: - >>> v1 = MultiVector([3, 4]) + >>> v1 = Vector([3, 4]) >>> x, y = v1 >>> x, y (3.0, 4.0) >>> v1 - MultiVector([3.0, 4.0]) + Vector([3.0, 4.0]) >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True @@ -29,27 +30,27 @@ Tests with 2-dimensions (same results as ``vector_v1.py``):: b'\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@' >>> abs(v1) 5.0 - >>> bool(v1), bool(MultiVector([0, 0])) + >>> bool(v1), bool(Vector([0, 0])) (True, False) Test of ``.frombytes()`` class method: - >>> v1_clone = MultiVector.frombytes(bytes(v1)) + >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone - MultiVector([3.0, 4.0]) + Vector([3.0, 4.0]) >>> v1 == v1_clone True Tests with 3-dimensions:: - >>> v1 = MultiVector([3, 4, 5]) + >>> v1 = Vector([3, 4, 5]) >>> x, y, z = v1 >>> x, y, z (3.0, 4.0, 5.0) >>> v1 - MultiVector([3.0, 4.0, 5.0]) + Vector([3.0, 4.0, 5.0]) >>> v1_clone = eval(repr(v1)) >>> v1 == v1_clone True @@ -57,32 +58,32 @@ Tests with 3-dimensions:: (3.0, 4.0, 5.0) >>> abs(v1) # doctest:+ELLIPSIS 7.071067811... - >>> bool(v1), bool(MultiVector([0, 0, 0])) + >>> bool(v1), bool(Vector([0, 0, 0])) (True, False) Tests with many dimensions:: - >>> v7 = MultiVector(range(7)) + >>> v7 = Vector(range(7)) >>> v7 - MultiVector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) + Vector([0.0, 1.0, 2.0, 3.0, 4.0, ...]) >>> abs(v7) # doctest:+ELLIPSIS 9.53939201... Test of ``.__bytes__`` and ``.frombytes()`` methods:: - >>> v1 = MultiVector([3, 4, 5]) - >>> v1_clone = MultiVector.frombytes(bytes(v1)) + >>> v1 = Vector([3, 4, 5]) + >>> v1_clone = Vector.frombytes(bytes(v1)) >>> v1_clone - MultiVector([3.0, 4.0, 5.0]) + Vector([3.0, 4.0, 5.0]) >>> v1 == v1_clone True Tests of sequence behavior:: - >>> v1 = MultiVector([3, 4, 5]) + >>> v1 = Vector([3, 4, 5]) >>> len(v1) 3 >>> v1[0], v1[len(v1)-1], v1[-1] @@ -91,22 +92,22 @@ Tests of sequence behavior:: Test of slicing:: - >>> v7 = MultiVector(range(7)) + >>> v7 = Vector(range(7)) >>> v7[-1] 6.0 >>> v7[1:4] - MultiVector([1.0, 2.0, 3.0]) + Vector([1.0, 2.0, 3.0]) >>> v7[-1:] - MultiVector([6.0]) + Vector([6.0]) >>> v7[1,2] Traceback (most recent call last): ... - TypeError: MultiVector indices must be integers + TypeError: Vector indices must be integers Tests of dynamic attribute access:: - >>> v7 = MultiVector(range(10)) + >>> v7 = Vector(range(10)) >>> v7.x 0.0 >>> v7.y, v7.z, v7.t, v7.u, v7.v, v7.w @@ -117,33 +118,33 @@ Dynamic attribute lookup failures:: >>> v7.k Traceback (most recent call last): ... - AttributeError: 'MultiVector' object has no attribute 'k' - >>> v3 = MultiVector(range(3)) + AttributeError: 'Vector' object has no attribute 'k' + >>> v3 = Vector(range(3)) >>> v3.t Traceback (most recent call last): ... - AttributeError: 'MultiVector' object has no attribute 't' + AttributeError: 'Vector' object has no attribute 't' >>> v3.spam Traceback (most recent call last): ... - AttributeError: 'MultiVector' object has no attribute 'spam' + AttributeError: 'Vector' object has no attribute 'spam' Tests of hashing:: - >>> v1 = MultiVector([3, 4]) - >>> v2 = MultiVector([3.1, 4.2]) - >>> v3 = MultiVector([3, 4, 5]) - >>> v6 = MultiVector(range(6)) + >>> v1 = Vector([3, 4]) + >>> v2 = Vector([3.1, 4.2]) + >>> v3 = Vector([3, 4, 5]) + >>> v6 = Vector(range(6)) >>> hash(v1), hash(v2), hash(v3), hash(v6) (7, 384307168202284039, 2, 1) >>> len(set([v1, v2, v3, v6])) 4 -Tests of ``format()`` with Cartesian coordinates in 2D: +Tests of ``format()`` with Cartesian coordinates in 2D:: - >>> v1 = MultiVector([3, 4]) + >>> v1 = Vector([3, 4]) >>> format(v1) '(3.0, 4.0)' >>> format(v1, '.2f') @@ -152,38 +153,35 @@ Tests of ``format()`` with Cartesian coordinates in 2D: '(3.000e+00, 4.000e+00)' -Tests of ``format()`` with Cartesian coordinates in 3D and 7D: +Tests of ``format()`` with Cartesian coordinates in 3D and 7D:: - >>> v3 = MultiVector([3, 4, 5]) + >>> v3 = Vector([3, 4, 5]) >>> format(v3) '(3.0, 4.0, 5.0)' - >>> format(MultiVector(range(7))) + >>> format(Vector(range(7))) '(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0)' -Tests of the ``angle`` method:: +Tests of ``format()`` with spherical coordinates in 2D, 3D and 4D:: - >>> MultiVector([0, 0]).angle() - 0.0 - >>> MultiVector([1, 0]).angle() - 0.0 - >>> epsilon = 10**-8 - >>> abs(MultiVector([0, 1]).angle() - math.pi/2) < epsilon - True - >>> abs(MultiVector([1, 1]).angle() - math.pi/4) < epsilon - True - - -Tests of ``format()`` with polar coordinates: - - >>> format(MultiVector([1, 1]), 'p') # doctest:+ELLIPSIS + >>> format(Vector([1, 1]), 'h') # doctest:+ELLIPSIS '<1.414213..., 0.785398...>' - >>> format(MultiVector([1, 1]), '.3ep') + >>> format(Vector([1, 1]), '.3eh') '<1.414e+00, 7.854e-01>' - >>> format(MultiVector([1, 1]), '0.5fp') + >>> format(Vector([1, 1]), '0.5fh') '<1.41421, 0.78540>' - - + >>> format(Vector([1, 1, 1]), 'h') # doctest:+ELLIPSIS + '<1.73205..., 0.95531..., 0.78539...>' + >>> format(Vector([2, 2, 2]), '.3eh') + '<3.464e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 0, 0]), '0.5fh') + '<0.00000, 0.00000, 0.00000>' + >>> format(Vector([-1, -1, -1, -1]), 'h') # doctest:+ELLIPSIS + '<2.0, 2.09439..., 2.18627..., 3.92699...>' + >>> format(Vector([2, 2, 2, 2]), '.3eh') + '<4.000e+00, 1.047e+00, 9.553e-01, 7.854e-01>' + >>> format(Vector([0, 1, 0, 1]), '0.5fh') + '<1.41421, 1.57080, 0.78540, 1.57080>' """ from array import array @@ -191,9 +189,10 @@ import reprlib import math import functools import operator +import itertools # <1> -class MultiVector: +class Vector: typecode = 'd' def __init__(self, components): @@ -205,7 +204,7 @@ class MultiVector: def __repr__(self): components = reprlib.repr(self._components) components = components[components.find('['):-1] - return 'MultiVector({})'.format(components) + return 'Vector({})'.format(components) def __str__(self): return str(tuple(self)) @@ -250,21 +249,31 @@ class MultiVector: msg = '{.__name__!r} object has no attribute {!r}' raise AttributeError(msg.format(cls, name)) - def angle(self): - return math.atan2(self.y, self.x) # <1> + def angle(self, n): # <2> + r = math.sqrt(sum(x * x for x in self[n:])) + a = math.atan2(r, self[n-1]) + if (n == len(self) - 1) and (self[-1] < 0): + return math.pi * 2 - a + else: + return a + + def angles(self): # <3> + return (self.angle(n) for n in range(1, len(self))) def __format__(self, fmt_spec=''): - if fmt_spec.endswith('p'): + if fmt_spec.endswith('h'): # hyperspherical coordinates fmt_spec = fmt_spec[:-1] - coords = (abs(self), self.angle()) - outer_fmt = '<{}>' # <2> + coords = itertools.chain([abs(self)], + self.angles()) # <4> + outer_fmt = '<{}>' # <5> else: coords = self - outer_fmt = '({})' # <3> + outer_fmt = '({})' # <6> components = (format(c, fmt_spec) for c in coords) - return outer_fmt.format(', '.join(components)) # <4> + return outer_fmt.format(', '.join(components)) # <7> @classmethod def frombytes(cls, octets): memv = memoryview(octets).cast(cls.typecode) return cls(memv) +# END VECTOR_V5