Source code for geoptics.elements.vector

# -*- coding: utf-8 -*-

# Copyright (C) 2014 ederag <edera@gmx.fr>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GeOptics; see the file LICENSE.txt.  If not, see
# <http://www.gnu.org/licenses/>.


"""Points and vectors in 2D."""


from math import atan2, sqrt


[docs]class Point(object): """2D point.""" __slots__ = ('x', 'y') def __init__(self, x=None, y=None): self.x = x self.y = y
[docs] @classmethod def from_config(cls, config): """Alternate constructor. Args: config (dict): Configuration dictionary Returns: Point: new Point instance Examples: >>> v1 = Point(10, 20) >>> config = v1.config >>> v2 = Point.from_config(config) >>> v2.config == config True """ return cls(**config)
[docs] def translate(self, dv=None, dx=0, dy=0): """Translate the point. The translation must be given as a single vector (``dv``) or with ``dx`` and/or ``dy``. If ``dv`` is given, ``dx`` and ``dy`` are discarded. For example:: >>> p = Point(30, 50) >>> dv = Vector(x=10, y=20) >>> p.translate(dv) Point(40, 70) is the same as:: >>> p = Point(30, 50) >>> p.translate(dx=10, dy=20) Point(40, 70) It is possible to change only one coordinate >>> p = Point(30, 50) >>> p.translate(dx=10) Point(40, 50) Beware that the transformation is done in-place. this can lead to side effects. For example, in order to leave p1 untouched, a `.copy()` has to be inserted:: >>> p2 = p.copy().translate(dx=10, dy=20) Args: dv (Vector, optional): Translation vector dx (number, optional): Translation amount along `x` dy (number, optional): Translation amount along `y` Returns: Point: The point has been modified in-place, but for convenience, the point itself is also returned """ if dv is not None: self.x += dv.x self.y += dv.y else: self.x += dx self.y += dy return self
@property def config(self): # noqa: D401 """Configuration dictionary.""" return {'x': self.x, 'y': self.y}
[docs] def copy(self): """Return an independent copy of the point.""" p = Point(self.x, self.y) return p
def __add__(self, other): # adding Points is meaningful for interpolation if isinstance(other, (Vector, Point)): return Point(self.x + other.x, self.y + other.y) else: raise NotImplementedError("Trying to add {}".format(type(other))) def __eq__(self, other): return (self.x == other.x) and (self.y == other.y) def __iter__(self): return iter((self.x, self.y)) def __mul__(self, other): if isinstance(other, (float, int)): return Point(self.x * other, self.y * other) else: raise NotImplementedError("Trying to add {}".format(type(other))) def __rmul__(self, other): return self.__mul__(other) def __repr__(self): return "Point({}, {})".format(self.x, self.y)
[docs]class Vector(object): """2D Vector.""" __slots__ = ('x', 'y') def __init__(self, x, y): self.x = x self.y = y @property def config(self): # noqa: D401 """Configuration dictionary.""" return {'x': self.x, 'y': self.y}
[docs] @classmethod def from_config(cls, config): """Alternate constructor. Args: config (dict): Configuration dictionary Returns: Vector: new Vector instance Examples: >>> v1 = Vector(10, 20) >>> config = v1.config >>> v2 = Vector.from_config(config) >>> v2.config == config True """ return cls(**config)
[docs] def norm(self): """Return the vector norm.""" return sqrt(self.x * self.x + self.y * self.y)
[docs] def normalize(self): """Normalize vector (divide it by its norm. The normalization is done in-place, but also return self, for convenience. """ norm = self.norm() self.x = self.x / norm self.y = self.y / norm return self
[docs] def normal(self, normalized=False): """Return a vector orthogonal to self.""" n = Vector(self.y, -self.x) if normalized: n.normalize() return n
[docs] def copy(self): """Return an independent copy of the vector.""" return Vector(self.x, self.y)
[docs] def colinear(self, other): """Return True if self and other are colinear.""" return (self.x * other.y - self.y * other.x) == 0
[docs] def theta_x(self): """Return the inclination angle over the x axis. Result in the ]-pi, pi] interval. """ return atan2(self.y, self.x)
def __imul__(self, other): # in-place multiplication for "*=" if isinstance(other, Vector): self.x *= other.x self.y *= other.y else: self.x *= other self.y *= other return self def __iter__(self): return iter((self.x, self.y)) def __mul__(self, other): if isinstance(other, Vector): # scalar product return self.x * other.x + self.y * other.y else: return Vector(self.x * other, self.y * other) def __neg__(self): return Vector(-self.x, -self.y) def __rmul__(self, other): return self.__mul__(other) def __add__(self, other): if isinstance(other, Vector): return Vector(self.x + other.x, self.y + other.y) else: raise NotImplementedError("Trying to add {}".format(type(other))) def __repr__(self): return "Vector({}, {})".format(self.x, self.y)
[docs]class Vector_M1M2(Vector): """Vector joining M1 to M2. Args: M1 (Point): starting point M2 (Point): end point """ __slots__ = () # prevent any dict to be created (~ half lighter) def __init__(self, M1, M2): Vector.__init__(self, M2.x - M1.x, M2.y - M1.y)