├── .gitignore ├── AUTHORS.rst ├── HISTORY.rst ├── LICENSE.rst ├── README.rst ├── examples ├── basic_example.py └── distance_between_linesegment_and_point.py ├── setup.cfg ├── setup.py └── vecpy ├── __init__.py └── vecpy.py /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j-i-l/VecPy/76ab705e21c42577ec0064ecd41d16375159b283/.gitignore -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | VecPy is written (and eventually maintained) by Jonas I. Liechti 2 | 3 | Main Authors 4 | ```````````` 5 | 6 | - Jonas I. Liechti `@j-i-l `_, Creator. 7 | 8 | 9 | Patches and Suggestions 10 | ``````````````````````` -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | .. :changelog: 2 | 3 | Release History 4 | --------------- 5 | 6 | 0.2.0 (2020-07-02) 7 | ++++++++++++++++++ 8 | 9 | * Allowing vectors of higher dimension (>3) 10 | * Adding support for `list` built-in methods like `append` or `len` 11 | * Adding possibility to change the dimension of a vector 12 | 13 | 0.1.6 (2015-07-02) 14 | ++++++++++++++++++ 15 | 16 | * BIRTH! 17 | 18 | 19 | 0.0.1 (2015-05-02) 20 | ++++++++++++++++++ 21 | 22 | * There are magic functions 23 | -------------------------------------------------------------------------------- /LICENSE.rst: -------------------------------------------------------------------------------- 1 | Copyright 2015 Jonas I. Liechti 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | VecPy: Some Linear Algebra Basics in Python 2 | =========================================== 3 | 4 | .. image:: https://img.shields.io/pypi/v/vecpy.svg 5 | :target: https://pypi.python.org/pypi/vecpy 6 | 7 | .. image:: https://img.shields.io/pypi/dm/vecpy.svg?style=flat-square 8 | :target: https://pypi.python.org/pypi/vecpy 9 | 10 | A quick one: 11 | 12 | .. code-block:: python 13 | 14 | >>> from vecpy import Vector as Vec 15 | >>> v = Vec(0, 2) 16 | >>> w = Vec([1, 3]) 17 | >>> v + 2 18 | >>> v + w 19 | 20 | Features 21 | -------- 22 | 23 | - Basic operations ( dot-product, projection, rescaling, etc.) 24 | 25 | 26 | Installation 27 | ------------ 28 | 29 | To install VecPy, simply: 30 | 31 | .. code-block:: bash 32 | 33 | $ pip install vecpy 34 | 35 | 36 | Documentation 37 | ------------- 38 | 39 | This is a simple package allowing to complete very basic linear algebra tasks. 40 | 41 | It is best explained by example: 42 | 43 | .. code-block:: python 44 | 45 | >>> v = Vec(0, 2) 46 | >>> w = Vec(1, 3) 47 | 48 | 49 | You can do basic rescaling of a vector: 50 | 51 | .. code-block:: python 52 | 53 | >>> v_twice = v ^ 2 54 | >>> print v_twice.length == 2 * v.length 55 | >>> v_unit = v ^ 0 56 | >>> print v_unit.length 57 | 58 | Adding scalar and other vectors: 59 | 60 | .. code-block:: python 61 | 62 | >>> v + 2 63 | >>> v + w 64 | ... 65 | 66 | 67 | Multiplication and dot-product 68 | 69 | .. code-block:: python 70 | 71 | >>> v * 3 72 | >>> v.dot(w) 73 | 74 | A vector has several properties: 75 | 76 | .. code-block:: python 77 | 78 | >>> v.length 79 | >>> v.dim 80 | 81 | You can specify which norm to use (default is the Euclidean) 82 | 83 | .. code-block:: python 84 | 85 | >>> v.norm(1) 86 | >>> v.norm('inf') 87 | >>> v.norm(2) == v.length 88 | ... 89 | 90 | You can project one vector on another: 91 | 92 | .. code-block:: python 93 | 94 | >>> w_proj_v = v.proj(w) 95 | >>> ratio = v.proj(w, get_scale=True) 96 | 97 | Iteration is supported as well: 98 | 99 | .. code-block:: python 100 | 101 | >>> print [xi for xi in v] 102 | 103 | String representations: 104 | 105 | .. code-block:: python 106 | 107 | >>> print str(v) 108 | >>> print '{:[x, y, z]}'.format(v) 109 | -------------------------------------------------------------------------------- /examples/basic_example.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Jonas I Liechti' 2 | from vecpy import Vector as Vec 3 | 4 | # define a vector: 5 | v = Vec(0, 2) # or Vec((0, 2)) or Vec([0, 2]) 6 | w = Vec(1, 3) 7 | 8 | # get a vector with twice the lengt (same direction) 9 | v_twice = v ^ 2 10 | 11 | # get the unit vector 12 | v_unit = v ^ 0 13 | v_unit = v.unit 14 | 15 | # adding scalars and vectors 16 | v + 2 # adds 2 to every coord 17 | v + w # adds coordinate by coordinate 18 | 19 | # multiply by scalar 20 | v * 3 # or 3 * v 21 | 22 | # dot product 23 | v.dot(w) 24 | 25 | # get a norm 26 | v.norm('inf') # the default is the Euclidean norm (p=2) 27 | 28 | # get the length of a vector 29 | v.length # this is just v.norm(2) 30 | 31 | # get the dimension 32 | v.dim 33 | 34 | # project one vector onto another 35 | w_proj_v = v.proj(w) 36 | 37 | # get length ration of a vector and the projectoin of another vector onto it 38 | ratio = v.proj(w, get_scale=True) 39 | 40 | # iterate through coordinates 41 | print [xi for xi in v] 42 | 43 | # string representation 44 | print str(v) 45 | print '{:[x, y, z]}'.format(v) 46 | -------------------------------------------------------------------------------- /examples/distance_between_linesegment_and_point.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Jonas I Liechti' 2 | from vecpy import Vector as Vec 3 | 4 | 5 | # How to get the distance between a segment and a point 6 | def point2segment_dist(point, segment): 7 | """ 8 | Return the distance between a segment of a line and a point 9 | 10 | :param point: 11 | :param segment: 12 | :return: 13 | """ 14 | sp_vec = Vec(segment[0], point) # create a vector from start of the segment to the point 15 | seg_vec = Vec(segment) # create a vector along the segment 16 | proj_scale = seg_vec.proj(sp_vec, True) # project the sp_vector to the segment vector and get the ration of the 17 | # length of those two parallel vectors 18 | if proj_scale <= 0: # if the projection has not the same direction 19 | dist = sp_vec.length 20 | elif proj_scale >= 1: # if the projection is longer than the segment vector 21 | dist = Vec(segment[1], point).length 22 | else: # get the length from the part of sg_vec orthogonal to the segment vector 23 | ortho_v = seg_vec.proj(sp_vec) - sp_vec 24 | dist = ortho_v.length 25 | return dist 26 | 27 | # line segment: 28 | x = [4, 5] 29 | y = [1, 1] 30 | my_seg = (x, y) 31 | # a point: 32 | p = [-1, -2] 33 | print point2segment_dist(p, my_seg) -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | from codecs import open 8 | 9 | try: 10 | from setuptools import setup 11 | except ImportError: 12 | from distutils.core import setup 13 | 14 | if sys.argv[-1] == 'publish': 15 | os.system('python setup.py sdist upload') 16 | sys.exit() 17 | 18 | packages = [ 19 | 'vecpy', 20 | ] 21 | 22 | requires = [] 23 | 24 | version = '' 25 | with open('vecpy/__init__.py', 'r') as fd: 26 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 27 | fd.read(), re.MULTILINE).group(1) 28 | 29 | if not version: 30 | raise RuntimeError('Cannot find version information') 31 | 32 | with open('README.rst', 'r', 'utf-8') as f: 33 | readme = f.read() 34 | # with open('HISTORY.rst', 'r', 'utf-8') as f: 35 | # history = f.read() 36 | 37 | setup( 38 | name='vecpy', 39 | version=version, 40 | description='Simple Vector package to help with basic linear algebra.', 41 | long_description=readme + '\n\n', # + history, 42 | author='Jonas I. Liechti', 43 | author_email='jon.liechti@gmail.com', 44 | url='https://github.com/j-i-l/VecPy', 45 | download_url='https://github.com/j-i-l/VecPy/tarball/0.2.0', 46 | keywords=['vector', 'linear algebra', 'projection'], 47 | packages=packages, 48 | package_data={'': ['LICENSE', 'HISTORY']}, 49 | package_dir={'vecpy': 'vecpy'}, 50 | include_package_data=True, 51 | install_requires=requires, 52 | license='Apache 2.0', 53 | zip_safe=False, 54 | classifiers=( 55 | 'Development Status :: 3 - Alpha', 56 | 'Intended Audience :: Developers', 57 | 'Natural Language :: English', 58 | 'License :: OSI Approved :: Apache Software License', 59 | 'Programming Language :: Python', 60 | 'Programming Language :: Python :: 2.7', 61 | 'Programming Language :: Python :: 3.6', 62 | 'Operating System :: OS Independent' 63 | ), 64 | # dependency_links = [ 65 | # "http://..." 66 | # ], 67 | ) 68 | -------------------------------------------------------------------------------- /vecpy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | __title__ = 'vecpy' 3 | __version__ = '0.2.0' 4 | #__build__ = 5 | __author__ = 'Jonas I Liechti' 6 | __license__ = 'Apache 2.0' 7 | __copyright__ = 'Copyright 2020 Jonas I. Liechti' 8 | """ 9 | 10 | 11 | :copyright: (c) 2020 by Jonas I. Liechti. 12 | :license: Apache 2.0, see LICENSE for more details. 13 | """ 14 | from .vecpy import Vector 15 | -------------------------------------------------------------------------------- /vecpy/vecpy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | vecpy.vecpy 5 | ~~~~~~~~~~~ 6 | This module contains the Vector object. 7 | 8 | """ 9 | __author__ = 'Jonas I Liechti' 10 | 11 | import warnings 12 | from collections import MutableSequence 13 | 14 | 15 | class Vector(MutableSequence): 16 | def __init__(self, *args, **kwargs): 17 | if args: 18 | if len(args) == 1: 19 | if isinstance(args[0], (list, tuple)): 20 | self._coords = list(args[0]) 21 | self._orig = [0]*len(args[0]) 22 | # todo: check the types of the elements in _coords_ 23 | else: 24 | self._coords = [args[0]] 25 | self._orig = [0] 26 | elif len(args) == 2 and all( 27 | [isinstance(arg, (list, tuple)) for arg in args]): 28 | # the vector is defined via two points (start and stop) 29 | # keep the two points 30 | self._orig = args[0] 31 | else: 32 | self._coords = list(args) 33 | self._orig = [0]*len(args) 34 | # if len(_coords) == 2: 35 | # _coords = list(_coords) + [0.] 36 | # self.x, self.y, self.z = self._coords 37 | else: 38 | _x = kwargs.get('x', None) 39 | if _x is not None: 40 | _coords = [_x] 41 | _y = kwargs.get('y', None) 42 | if _y is not None: 43 | _coords.append(_y) 44 | _z = kwargs.get('z', None) 45 | if _z is not None: 46 | _coords.append(_z) 47 | self._coords = _coords 48 | self._orig = [0]*len(self._coords) 49 | else: 50 | self._coords = kwargs.pop('end_point', list()) 51 | self._orig = kwargs.pop('start_point', [0]*len(self._coords)) 52 | self._coords = [d[1]-d[0] for d in zip(self._orig, self._coords)] 53 | 54 | def __len__(self): 55 | return len(self._coords) 56 | 57 | def __getitem__(self, index): 58 | return self._coords[index] 59 | 60 | def __setitem__(self, index, value): 61 | self._coords.__setitem__(index, value) 62 | 63 | def __delitem__(self, index): 64 | if index == len(self._coords) - 1 or index == -1: 65 | self._coords.__delitem__(index) 66 | else: 67 | self._coords[index] = 0 68 | warnings.warn(""" 69 | Deletion of a coordinate is only possible for the last coordinate, as deletion 70 | of an intermediary coordinate would break consistency. Consider for example 71 | deleting the y-coord in a 3 dimensional vector. If it were simply removed from 72 | the list of coordinates then the old z-coordinate would move to the location of 73 | the old y-coordinate and thus become the new y-coordinate. 74 | Therefore you can only remove coordinates at the very end of the list of 75 | coordinates. At any other position the attempt to remove the coordinate will 76 | simply set its value to 0. So: 77 | >>> my_vec = Vec(1,2,3) 78 | >>> del(my_vec[1]) 79 | >>> my_vec 80 | [1, 0, 3] 81 | """) 82 | 83 | def insert(self, index, value): 84 | if index >= len(self._coords): 85 | self._coords.insert(index, value) 86 | warnings.warn( 87 | "The dimension of the vector was increased!" 88 | ) 89 | else: 90 | raise ValueError( 91 | """You can only increase the dimensionality of a vector by 92 | adding another dimension at the end of the existing 93 | coordinates.\nTo do so either use `append` or `extend`. 94 | """ 95 | ) 96 | 97 | @property 98 | def x(self): 99 | if self.dim > 3: 100 | raise Warning( 101 | 'You are accessing the x-coordinate of a vector of ' 102 | 'length {0}, consider using the getitem syntax instead: vex[0]' 103 | ) 104 | return self._coords[0] 105 | 106 | @property 107 | def y(self): 108 | if self.dim > 3: 109 | raise Warning( 110 | 'You are accessing the x-coordinate of a vector of ' 111 | 'length {0}, consider using the getitem syntax instead: vex[1]' 112 | ) 113 | try: 114 | return self._coords[1] 115 | except IndexError: 116 | raise IndexError( 117 | 'Attempting to access dimension {0} ' 118 | 'for a Vector is of dimension {1}'.format(2, len(self)) 119 | ) 120 | 121 | @property 122 | def z(self): 123 | if self.dim > 3: 124 | raise Warning( 125 | 'You are accessing the x-coordinate of a vector of ' 126 | 'length {0}, consider using the getitem syntax instead: vex[2]' 127 | ) 128 | try: 129 | return self._coords[2] 130 | except IndexError: 131 | raise IndexError( 132 | 'Attempting to access dimension {0} ' 133 | 'for a Vector is of dimension {1}'.format(3, len(self)) 134 | ) 135 | 136 | @property 137 | def coords(self): 138 | return self.x, self.y, self.z 139 | 140 | @property 141 | def length(self): 142 | return self.norm(2) 143 | 144 | @property 145 | def unit(self): 146 | return self ^ 0 147 | 148 | @property 149 | def dim(self): 150 | return len(self._coords) 151 | 152 | def __iter__(self): 153 | return self._coords.__iter__() 154 | 155 | def dot(self, w): 156 | """ The dot product of self and other vector w. 157 | """ 158 | return sum([xi_s * xi_w for xi_s, xi_w in zip(self, w)]) 159 | 160 | def __add__(self, w): 161 | if isinstance(w, (int, float)): 162 | return Vector([xi + w for xi in self]) 163 | else: 164 | return Vector([xi_s + xi_w for xi_s, xi_w in zip(self, w)]) 165 | 166 | def __radd__(self, w): 167 | return self.__add__(w) 168 | 169 | def __sub__(self, w): 170 | return Vector([xi_s - xi_w for xi_s, xi_w in zip(self, w)]) 171 | 172 | def __mul__(self, w): 173 | """ Returns the dot product of self and other if multiplied 174 | by another Vector. If multiplied by an int or float, 175 | multiplies each component by other. 176 | """ 177 | if isinstance(w, (float, int)): 178 | return Vector([w * _xi for _xi in self]) 179 | else: 180 | return self.dot(w) 181 | 182 | def __rmul__(self, w): 183 | return self.__mul__(w) 184 | 185 | def __xor__(self, fct): 186 | """ 187 | Returns a new rescaled Vector 188 | 189 | E.g.: 190 | - vec ^ 1 returns a new Vector object with the same 191 | coordinates as vec 192 | - vec ^ 0 returns the unit vector in the direction 193 | of vec as a new Vector object 194 | - vec ^ 2 returns a new Vector object that has twice 195 | the length of vec and points in the same direction 196 | 197 | :return: 198 | """ 199 | if fct: 200 | _scale = fct 201 | else: 202 | _scale = 1 / self.length 203 | # to do: handle error 204 | return Vector([_xi * _scale for _xi in self]) 205 | 206 | def __str__(self): 207 | return str([_xi for _xi in self]) 208 | 209 | def __format__(self, to_format): 210 | if any([_xi in to_format for _xi in ['x', 'y', 'z']]): 211 | back_string = to_format.replace( 212 | 'x', '{}'.format(self.x) 213 | ).replace( 214 | 'y', '{}'.format(self.y) 215 | ).replace( 216 | 'z', '{}'.format(self.z) 217 | ) 218 | else: 219 | back_string = str(self) 220 | return back_string 221 | 222 | def norm(self, p=2): 223 | if p == 'inf': 224 | return max([abs(_xi) for _xi in self]) 225 | else: 226 | return float( 227 | sum([abs(_xi) ** p for _xi in self]) ** (1 / float(p)) 228 | ) 229 | 230 | def proj(self, w, get_scale=False): 231 | """ 232 | Project the Vector w onto the vector self 233 | Returns the projection of w onto self, i.e. another Vector 234 | if get_scale=True not a vector, but the rescaling factor is returned 235 | :param w: 236 | :return: 237 | """ 238 | scale_fact = self.dot(w) / self.length ** 2 239 | if get_scale: 240 | return scale_fact 241 | else: 242 | return self ^ scale_fact 243 | 244 | def angle(self, w, degree=False): 245 | """ 246 | Returns the angle (in radians by default) 247 | """ 248 | from math import acos, pi 249 | cos_theta = self.dot(w) / (self.length * w.length) 250 | theta = acos(cos_theta) 251 | if not degree: 252 | return theta 253 | else: 254 | return theta / (2 * pi) * 360 255 | --------------------------------------------------------------------------------