Source code for mathematics

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Mathematical functions and objects.

:Date: 2019-12-12

.. module:: mathematics
  :platform: *nix, Windows
  :synopsis: Mathematical functions and objects.

.. moduleauthor:: Daniel Weschke <daniel.weschke@directbox.de>
"""
import math
from math import gcd

[docs]def lcm(a, b): """Compute the lowest common multiple of a and b""" return a/gcd(a, b)*b
[docs]class vector(list): """Use/create vector like lists * size -> len(a) * abs -> abs(a) * dot -> a * b * outer -> a @ b use super constructor use super __iter__ use super __setitem__ >>> v = vector([1,2,3,4,5]) >>> v[3:5] = [1,2] >>> print(v) [1, 2, 3, 1, 2] >>> isinstance(v, vector) True use super __lt__(a, b) use super __le__(a, b) use super __eq__(a, b) >>> v = vector([1,2,3,1,2]) >>> v2 = vector([1,2,3,1,2]) >>> v == v2 True use super __ne__(a, b) use super __ge__(a, b) use super __gt__(a, b) use super __contains__ >>> 2 in vector([1,2,3]) True __isub__ >>> v = vector([1,2,3]) >>> v -= vector([3,3,3]) >>> print(v) [-2, -1, 0] __imul__ >>> v = vector([1,2,3]) >>> v *= vector([3,3,3]) >>> print(v) 18 __imatmul__ >>> m = vector([1,2,3]) >>> m *= vector([3,3,3]) >>> print(v) [[3, 3, 3], [6, 6, 6], [9, 9, 9]] """ def __getitem__(self, index): """ For index return value, for range return new vector object. :Example: >>> v = vector([1, 2, 3, 4, 5]) >>> v[1:3] [2, 3] >>> v = vector([1, 2, 3, 4, 5]) >>> v[3] 4 """ # use the list.__getslice__ method and convert result to vector item = super().__getitem__(index) return vector(item) if isinstance(item, list) else item def __pos__(self): """+ a (new object) """ return vector([*self]) def __neg__(self): """- a (new object) """ return vector([-i for i in self]) def __add__(self, other): """a + b (new object) :Example: >>> v = vector([1,2,3]) + vector([1,2,3]) >>> print(v) [2, 4, 6] """ return vector([i+j for i, j in zip(self, other)]) def __iadd__(self, other): """a += b (new object) :Example: >>> v = vector([1,2,3]) >>> v += vector([3,3,3]) >>> print(v) [4, 5, 6] """ return vector([i+j for i, j in zip(self, other)]) def __sub__(self, other): """a - b (new object) :Example: >>> v = vector([1,2,3]) - vector([1,2,3]) >>> print(v) [0, 0, 0] """ return vector([i-j for i, j in zip(self, other)]) def __mul__(self, other): r"""Scalar multiplication, dot product (inner product) or vector-matrix multiplication. a * b (new object) :type other: scalar, vector (or 1d list), matrix (or 2d list) .. math:: \mathbf{c} &= \mathbf{a} \cdot b \\ \mathbf{c} &= \mathbf{a} \cdot \mathbf{b} \\ \mathbf{c} &= \mathbf{a} \cdot \mathbf{B} .. note:: No size checking will be conducted, therefore no exceptions for wrong usage (result will be nonsense). :Example: >>> v = vector([1,2,3,4,5])*3 >>> print(v) [3, 6, 9, 12, 15] >>> v = vector([1,2,3,4,5])*3. >>> print(v) [3.0, 6.0, 9.0, 12.0, 15.0] >>> s = vector([1,2,3])*vector([1,2,3]) >>> print(s) 14 .. seealso:: :meth:`__rmul__` """ try: # vector * vector return sum([i*j for i, j in zip(self, other)]) except: try: # vector * matrix return vector([sum(c*d for c, d in zip(self, b_col)) for b_col in zip(*other)]) except: # vector * scalar return vector([i*other for i in self]) def __rmul__(self, other): r"""Scalar multiplication, dot product (inner product) or matrix-vector multiplication. a * b (new object) :type other: scalar (or 1d list and 2d list) .. math:: \mathbf{c} &= a \cdot \mathbf{b} \\ \mathbf{c} &= \mathbf{a} \cdot \mathbf{b} \\ \mathbf{c} &= \mathbf{A} \cdot \mathbf{b} .. note:: No size checking will be conducted, therefore no exceptions for wrong usage (result will be nonsense). :Example: >>> v = 3*vector([1,2,3,4,5]) >>> print(v) [3, 6, 9, 12, 15] >>> v = 3.*vector([1,2,3,4,5]) >>> print(v) [3.0, 6.0, 9.0, 12.0, 15.0] .. seealso:: :meth:`__mul__` :meth:`matrix.__mul__` for matrix * vector """ try: # 2d list * vector (matrix * vector see matrix.__mul__) return vector([sum(c*d for c, d in zip(a_row, self)) for a_row in other]) except: # scalar * vector return self*other def __matmul__(self, other): r"""Outer product a @ b (new object) .. math:: \vec{a} \otimes \vec{b} = \begin{pmatrix} a_{1}\\ a_{2}\\ a_{3} \end{pmatrix} \otimes \begin{pmatrix} b_{1}\\ b_{2}\\ b_{3} \end{pmatrix} = \begin{pmatrix} a_{1}b_{1}&a_{1}b_{2}&a_{1}b_{3}\\ a_{2}b_{1}&a_{2}b_{2}&a_{2}b_{3}\\ a_{3}b_{1}&a_{3}b_{2}&a_{3}b_{3} \end{pmatrix} :Example: >>> m = vector([1,2,3]) @ vector([1,2,4]) >>> print(m) [[1, 2, 4], [2, 4, 8], [3, 6, 12]] """ try: # vector * vector return matrix([[i*j for j in other] for i in self]) except: # vector * number return self*other def __abs__(self): r"""Magnitude / norm of a vector .. math:: b = \sqrt{\sum a_i^2} :Example: >>> norm([3, 4]) 5 >>> norm(vector([3, 4])) 5 """ return math.sqrt(self * self) def __str__(self): return str([*self]) def __repr__(self): return "vector(" + str(self) + ")"
[docs] @staticmethod def full(length, fill_value): """Returns a vector of length m or matrix of size m rows, n columns filled with v. :param length: length of the vector, e. g. 3 :type length: int :param fill_value: Fill value :Type fill_value: scalar :Example: >>> v = vector.full(3, 7) >>> print(v) [7, 7, 7] """ return vector([fill_value for i in range(length)])
[docs] @staticmethod def zeros(length): """Returns a zero vector of length m or matrix of size rows, n columns filled with zeros. :param length: length of the vector, e. g. 3 :type length: int :Example: >>> v = zeros(3) >>> print(v) [0.0, 0.0, 0.0] """ return vector.full(length, 0.)
[docs] @staticmethod def ones(length): """Returns a vector of length m or matrix of size rows, n columns filled with ones. :param length: lhape of the vector, e. g. 3 :type length: int :Example: >>> v = ones(3) >>> print(v) [1.0, 1.0, 1.0] """ return vector.full(length, 1.)
[docs] @staticmethod def random(shape, lmin=0.0, lmax=1.0): """Returns a random vector of length n or matrix of size m rows, n columns filled with random numbers. :Example: >>> v = random(3) >>> print(v) [0.9172905912930438, 0.8908124278322492, 0.5256002790725927] >>> v = random(3, 1, 2) >>> print(v) [1.2563665665080803, 1.9270454509964547, 1.2381672401270487] """ import random dl = lmax-lmin return vector([dl*random.random()+lmin for i in range(shape)])
[docs] @staticmethod def normalize(a): r"""Normalize a vector (i. e. the vector has a length of 1) :type a: vector .. math:: \vec{e}_a = \frac{\vec{a}}{|\vec{a}|} .. seealso:: :meth:`norm` for a norm (magnitude) of a vector """ a_mag = abs(a) return vector([i/a_mag for i in a])
[docs] @staticmethod def ang(a, b): return math.acos(a * b / (abs(a) * abs(b)))
[docs] @staticmethod def conjugate(a): """ New vector object :type a: list """ return vector([i.conjugate() for i in a])
[docs] @staticmethod def re(a): """Return the real parts of a complex vector :type a: list """ return vector([i.real for i in a])
[docs] @staticmethod def im(a): """Return the imaginary parts of a complex vector :type a: list """ return vector([i.imag for i in a])
[docs] @staticmethod def abs(a): """Return modulus parts of a complex vector :type a: list """ return vector([abs(i) for i in a])
[docs] @staticmethod def arg(a): """Return phase parts of a complex vector :type a: list """ return vector([math.atan2(i.imag, i.real) for i in a])
[docs] @staticmethod def cross(a, b): r"""Cross product :type a: list :type b: list c is orthogonal to both a and b. The direction of c can be found with the right-hand rule. .. math:: \vec{c} = \vec{a} \times \vec{b} """ return [a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0]]
[docs] def xyz(self): return self[:3]
[docs] def rotate_x(self, theta): r"""Rotation about the x dirction. .. math:: \begin{bmatrix}x' \\ y' \\ z' \\ h'\end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta & 0 \\ 0 & \sin \theta & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\ y \\ z \\ h\end{bmatrix} """ self[:] = matrix.rx(theta) * self return self
[docs] def rotate_y(self, theta): r"""Rotation about the y dirction. .. math:: \begin{bmatrix}x' \\ y' \\ z' \\ h'\end{bmatrix} = \begin{bmatrix} \cos \theta & 0 & \sin \theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \theta & 0 & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\ y \\ z \\ h\end{bmatrix} """ self[:] = matrix.ry(theta) * self return self
[docs] def rotate_z(self, theta): r"""Rotation about the z dirction. .. math:: \begin{bmatrix}x' \\ y' \\ z' \\ h'\end{bmatrix} = \begin{bmatrix} \cos \theta & -\sin \theta & 0 & 0 \\ \sin \theta & \cos \theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\ y \\ z \\ h\end{bmatrix} """ self[:] = matrix.rz(theta) * self return self
[docs] def translate(self, tx, ty, tz): r"""Translation .. math:: \begin{bmatrix}x' \\ y' \\ z' \\ h'\end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\ y \\ z \\ h\end{bmatrix} """ self[:] = matrix.t(tx, ty, tz) * self return self
[docs] def scale(self, sx, sy=None, sz=None): r"""Scaling uniform scaling if sx=sy=sz=s. Note that scaling happens around the origin, so objects not centered at the origin will have their centers move. To avoid this, either scale the object when it's located at the origin, or perform a translation afterwards to move the object back to where it should be. .. math:: \begin{bmatrix}x' \\ y' \\ z' \\ h'\end{bmatrix} = \begin{bmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\ y \\ z \\ h\end{bmatrix} """ if not sy: sy = sx sz = sx self[:] = matrix.s(sx, sy, sz) * self return self
[docs] def ch_cs(self, cs): r"""Transform this vector from its defined coordinate system to a new coordinate system, defined by the given coordinate system (u, v and w direction vectors). .. math:: \begin{bmatrix}x' \\ y' \\ z' \\ h'\end{bmatrix} = \begin{bmatrix} u_x & u_y & u_z & 0 \\ v_x & v_y & v_z & 0 \\ w_x & w_y & w_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\ y \\ z \\ h\end{bmatrix} """ self[:] = cs * self return self
[docs]class matrix(list): """Use/create matrix like list of lists """ def __getitem__(self, index): """ For index return value, for range return new vector object. :Example: >>> m = matrix([[1, 2, 3, 0], [4, 5, 6, 0], [7, 8, 9, 0], [0, 0, 0, 0]]) >>> print(m[2]) [7, 8, 9, 0] >>> print(m[:,1:3]) [[2, 3], [5, 6], [8, 9], [0, 0]] >>> print(m[0:2,1:3]) [[2, 3], [5, 6]] >>> print(m[::2,::2]) [[1, 3], [7, 9]] """ # index: slice(stop), slice(start, stop[, step]) # for e. g. m[(1,3),:] -> index = ((1, 3), slice(None, None, None)) # use the list.__getslice__ method and convert result to vector try: # 2d slicing (tuple of sclices) item = [row.__getitem__(index[1]) for row in super().__getitem__(index[0])] except: # 1d slicing item = super().__getitem__(index) return matrix(item) if isinstance(item, list) else item
[docs] @staticmethod def rx(theta): r"""Rotation matrix about the x direction. Rotates the coordinate system of vectors .. math:: R_{x}(\theta) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta & 0 \\ 0 & \sin \theta & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} """ s = math.sin(theta) c = math.cos(theta) T = matrix([[ 1, 0, 0, 0], [ 0, c,-s, 0], [ 0, s, c, 0], [ 0, 0, 0, 1]]) return T
[docs] @staticmethod def ry(theta): r"""Rotation matrix about the y direction. Rotates the coordinate system of vectors .. math:: R_{y}(\theta) = \begin{bmatrix} \cos \theta & 0 & \sin \theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \theta & 0 & \cos \theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} """ s = math.sin(theta) c = math.cos(theta) T = matrix([[ c, 0, s, 0], [ 0, 1, 0, 0], [-s, 0, c, 0], [ 0, 0, 0, 1]]) return T
[docs] @staticmethod def rz(theta): r"""Rotation matrix about the z direction. Rotates the coordinate system of vectors .. math:: R_{z}(\theta) = \begin{bmatrix} \cos \theta & -\sin \theta & 0 & 0 \\ \sin \theta & \cos \theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} :Example: .. math:: R_{z}(\theta) \begin{bmatrix}1 \\ 0 \\ 0 \\ 1\end{bmatrix} = \begin{bmatrix} \cos 90° & -\sin 90° & 0 & 0 \\ \sin 90° & \cos 90° & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}1 \\ 0 \\ 0 \\ 1\end{bmatrix} = \begin{bmatrix} 0 & -1 & 0 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}1 \\ 0 \\ 0 \\ 1\end{bmatrix} = \begin{bmatrix}0 \\ 1 \\ 0 \\ 1\end{bmatrix} """ s = math.sin(theta) c = math.cos(theta) T = matrix([[ c,-s, 0, 0], [ s, c, 0, 0], [ 0, 0, 1, 0], [ 0, 0, 0, 1]]) return T
[docs] @staticmethod def t(tx, ty, tz): r"""Translation matrix .. math:: T = \begin{bmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{bmatrix} """ T = matrix([[ 1, 0, 0,tx], [ 0, 1, 0,ty], [ 0, 0, 1,tz], [ 0, 0, 0, 1]]) return T
[docs] @staticmethod def s(sx, sy=None, sz=None): r"""Scaling matrix uniform scaling if sx=sy=sz=s. Note that scaling happens around the origin, so objects not centered at the origin will have their centers move. To avoid this, either scale the object when it's located at the origin, or perform a translation afterwards to move the object back to where it should be. .. math:: S = \begin{bmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} """ if not sy: sy = sx sz = sx T = matrix([[sx, 0, 0, 0], [ 0,sy, 0, 0], [ 0, 0,sz, 0], [ 0, 0, 0, 1]]) return T
def __mul__(self, other): r"""Scalar multiplication, dot product (inner product) or matrix-vector multiplication. a * b (new object) :type other: scalar, vector (or 1d list), matrix (or 2d list) .. math:: \mathbf{C} &= \mathbf{A} \cdot b \\ \mathbf{c} &= \mathbf{A} \cdot \mathbf{b} \\ \mathbf{C} &= \mathbf{A} \cdot \mathbf{B} .. note:: No size checking will be conducted, therefore no exceptions for wrong usage (result will be nonsense). :Example: >>> m = matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [0, 0, 0, 1]]) * 5 >>> print(m) [[5, 10, 15, 20], [25, 30, 35, 40], [45, 50, 55, 60], [0, 0, 0, 5]] >>> m = matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [0, 0, 0, 1]]) * 5. >>> print(m) [[5.0, 10.0, 15.0, 20.0], [25.0, 30.0, 35.0, 40.0], [45.0, 50.0, 55.0, 60.0], [0.0, 0.0, 0.0, 5.0]] >>> v = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) * vector([12, 12, 13]) >>> print(v) [75, 186, 297] >>> m = matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) * matrix([[12, 12, 13], [14, 15, 16], [17, 18, 19]]) >>> print(m) [[91, 96, 102], [220, 231, 246], [349, 366, 390]] .. seealso:: :meth:`__rmul__` """ try: # matrix * matrix return matrix([[sum(c*d for c, d in zip(a_row, b_col)) for b_col in zip(*other)] for a_row in self]) except: try: # matrix * vector return vector([sum(c*d for c, d in zip(a_row, other)) for a_row in self]) except: # matrix * scalar return matrix([[a*other for a in a_row] for a_row in self]) def __rmul__(self, other): r"""Scalar multiplication, dot product (inner product) or vector-matrix multiplication. a * b (new object) :type other: scalar (or 1d list and 2d list) .. math:: \mathbf{C} &= a \cdot \mathbf{B} \\ \mathbf{c} &= \mathbf{a} \cdot \mathbf{B} \\ \mathbf{C} &= \mathbf{A} \cdot \mathbf{B} .. note:: No size checking will be conducted, therefore no exceptions for wrong usage (result will be nonsense). :Example: >>> m = 5 * matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [0, 0, 0, 1]]) * 5 >>> print(m) [[5, 10, 15, 20], [25, 30, 35, 40], [45, 50, 55, 60], [0, 0, 0, 5]] >>> m = 5. * matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [0, 0, 0, 1]]) >>> print(m) [[5.0, 10.0, 15.0, 20.0], [25.0, 30.0, 35.0, 40.0], [45.0, 50.0, 55.0, 60.0], [0.0, 0.0, 0.0, 5.0]] .. seealso:: :meth:`__mul__` :meth:`vector.__mul__` for vector * matrix """ try: # 2d list * matrix (matrix * matrix see matrix.__mul__) return matrix([[sum(c*d for c, d in zip(a_row, b_col)) for b_col in zip(*self)] for a_row in other]) except: try: # 1d list * matrix (vector * matrix see vector.__mul__) return vector([sum(c*d for c, d in zip(other, b_col)) for b_col in zip(*self)]) except: # scalar * vector return self*other def __str__(self): return str([*self]) def __repr__(self): return "matrix(" + str(self) + ")"
[docs] def rotate_x(self, theta): r"""Rotation about the x dirction. .. math:: \begin{bmatrix} xx & xy & xz & t_x \\ yx' & yy' & yz' & t_y' \\ zx' & zy' & zz' & t_z' \\ 0 & 0 & 0 & h \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos \theta & -\sin \theta & 0 \\ 0 & \sin \theta & \cos \theta & 0 \\ 0 & 0 & 0 & h \end{bmatrix} \begin{bmatrix} xx & xy & xz & t_x \\ yx & yy & yz & t_y \\ zx & zy & zz & t_z \\ 0 & 0 & 0 & h \end{bmatrix} """ self[:] = matrix.rx(theta) * self return self
[docs] def rotate_y(self, theta): r"""Rotation about the y dirction. .. math:: \begin{bmatrix} xx' & xy' & xz' & t_x' \\ yx & yy & yz & t_y \\ zx' & zy' & zz' & t_z' \\ 0 & 0 & 0 & h \end{bmatrix} = \begin{bmatrix} \cos \theta & 0 & \sin \theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin \theta & 0 & \cos \theta & 0 \\ 0 & 0 & 0 & h \end{bmatrix} \begin{bmatrix} xx & xy & xz & t_x \\ yx & yy & yz & t_y \\ zx & zy & zz & t_z \\ 0 & 0 & 0 & h \end{bmatrix} """ self[:] = matrix.ry(theta) * self return self
[docs] def rotate_z(self, theta): r"""Rotation about the z dirction. .. math:: \begin{bmatrix} xx' & xy' & xz' & t_x' \\ yx' & yy' & yz' & t_y' \\ zx & zy & zz & t_z \\ 0 & 0 & 0 & h \end{bmatrix} = \begin{bmatrix} \cos \theta & -\sin \theta & 0 & 0 \\ \sin \theta & \cos \theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} xx & xy & xz & t_x \\ yx & yy & yz & t_y \\ zx & zy & zz & t_z \\ 0 & 0 & 0 & h \end{bmatrix} """ self[:] = matrix.rz(theta) * self return self
[docs] def translate(self, tx, ty, tz): r"""Translation .. math:: \begin{bmatrix} xx & xy & xz & t_x' \\ yx & yy & yz & t_y' \\ zx & zy & zz & t_z' \\ 0 & 0 & 0 & h \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} xx & xy & xz & t_x \\ yx & yy & yz & t_y \\ zx & zy & zz & t_z \\ 0 & 0 & 0 & h \end{bmatrix} """ self[:] = matrix.t(tx, ty, tz) * self return self
[docs] def scale(self, sx, sy=None, sz=None): r"""Scaling uniform scaling if sx=sy=sz=s. Note that scaling happens around the origin, so objects not centered at the origin will have their centers move. To avoid this, either scale the object when it's located at the origin, or perform a translation afterwards to move the object back to where it should be. .. math:: \begin{bmatrix} xx' & xy' & xz' & t_x' \\ yx' & yy' & yz' & t_y' \\ zx' & zy' & zz' & t_z' \\ 0 & 0 & 0 & h \end{bmatrix} = \begin{bmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} xx & xy & xz & t_x \\ yx & yy & yz & t_y \\ zx & zy & zz & t_z \\ 0 & 0 & 0 & h \end{bmatrix} """ if not sy: sy = sx sz = sx self[:] = matrix.s(sx, sy, sz) * self return self