From ea72675c0a8bd6292738244e6161d9343972e251 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Thu, 27 Jul 2023 21:30:35 +0900 Subject: [PATCH 01/11] add reverse method like `__radd__`, `__rmul__`, etc. This commit works in progress --- .../math/function/float/function1d/base.pyx | 163 ++++++++------ raysect/core/math/normal.pyx | 96 ++++---- raysect/core/math/point.pyx | 69 +++--- raysect/core/math/quaternion.pyx | 82 ++++--- raysect/core/math/vector.pyx | 205 +++++++++++------- 5 files changed, 334 insertions(+), 281 deletions(-) diff --git a/raysect/core/math/function/float/function1d/base.pyx b/raysect/core/math/function/float/function1d/base.pyx index 2426f3cd..c1d86d4d 100644 --- a/raysect/core/math/function/float/function1d/base.pyx +++ b/raysect/core/math/function/float/function1d/base.pyx @@ -65,104 +65,123 @@ cdef class Function1D(FloatFunction): def __repr__(self): return 'Function1D(x)' - def __add__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() + b() - return AddFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() + B -> B + a() - return AddScalar1D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A + b() - return AddScalar1D( a, b) + def __add__(self, object b): + if is_callable(b): + # a() + b() + return AddFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() + B -> B + a() + return AddScalar1D( b, self) return NotImplemented - - def __sub__(object a, object b): + + def __radd__(self, object a): + return self.__add__(a) + + def __sub__(self, object b): + if is_callable(b): + # a() - b() + return SubtractFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() - B -> -B + a() + return AddScalar1D(-( b), self) + return NotImplemented + + def __rsub__(self, object a): if is_callable(a): - if is_callable(b): - # a() - b() - return SubtractFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() - B -> -B + a() - return AddScalar1D(-( b), a) + # a() - b() + return SubtractFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A - b() - return SubtractScalar1D( a, b) + # A - b() + return SubtractScalar1D( a, self) return NotImplemented - def __mul__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() * b() - return MultiplyFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() * B -> B * a() - return MultiplyScalar1D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return MultiplyScalar1D( a, b) + def __mul__(self, object b): + if is_callable(b): + # a() * b() + return MultiplyFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() * B -> B * a() + return MultiplyScalar1D( b, self) return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) @cython.cdivision(True) - def __truediv__(object a, object b): + def __truediv__(self, object b): cdef double v + if is_callable(b): + # a() / b() + return DivideFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() / B -> 1/B * a() + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") + return MultiplyScalar1D(1/v, self) + return NotImplemented + + @cython.cdivision(True) + def __rtruediv__(self, object a): if is_callable(a): - if is_callable(b): - # a() / b() - return DivideFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() / B -> 1/B * a() - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") - return MultiplyScalar1D(1/v, a) + # a() / b() + return DivideFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return DivideScalar1D( a, b) + # A / b() + return DivideScalar1D( a, self) return NotImplemented - def __mod__(object a, object b): + def __mod__(self, object b): cdef double v + if is_callable(b): + # a() % b() + return ModuloFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() % B + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") + return ModuloFunctionScalar1D(self, v) + return NotImplemented + + def __rmod__(self, object a): if is_callable(a): - if is_callable(b): - # a() % b() - return ModuloFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() % B - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") - return ModuloFunctionScalar1D(a, v) + # a() % b() + return ModuloFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A % b() - return ModuloScalarFunction1D( a, b) + # A % b() + return ModuloScalarFunction1D( a, self) return NotImplemented def __neg__(self): return MultiplyScalar1D(-1, self) - def __pow__(object a, object b, object c): + def __pow__(self, object b, object c): + if c is not None: + # Optimised implementation of pow(a, b, c) not available: fall back + # to general implementation + return PowFunction1D(self, b) % c + + if is_callable(b): + # a() ** b() + return PowFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() ** b + return PowFunctionScalar1D(self, b) + return NotImplemented + + def __rpow__(self, object a, object c): if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation - return (a ** b) % c + return PowFunction1D(a, self) % c + if is_callable(a): - if is_callable(b): - # a() ** b() - return PowFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() ** b - return PowFunctionScalar1D(a, b) + # a() ** b() + return PowFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # a ** b() - return PowScalarFunction1D( a, b) + # A ** b() + return PowScalarFunction1D( a, self) return NotImplemented def __abs__(self): diff --git a/raysect/core/math/normal.pyx b/raysect/core/math/normal.pyx index c0045a7b..6334c781 100644 --- a/raysect/core/math/normal.pyx +++ b/raysect/core/math/normal.pyx @@ -93,89 +93,96 @@ cdef class Normal3D(_Vec3): -self.y, -self.z) - def __add__(object x, object y): + def __add__(self, object y): """Addition operator.""" - cdef _Vec3 vx, vy + cdef _Vec3 v - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x - vy = <_Vec3>y + v = <_Vec3>y - return new_normal3d(vx.x + vy.x, - vx.y + vy.y, - vx.z + vy.z) + return new_normal3d(self.x + v.x, + self.y + v.y, + self.z + v.z) else: return NotImplemented + + def __radd__(self, object x): + """Reverse addition operator.""" + + return self.__add__(x) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtract operator.""" - cdef _Vec3 vx, vy + cdef _Vec3 v - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x - vy = <_Vec3>y + v = <_Vec3>y - return new_normal3d(vx.x - vy.x, - vx.y - vy.y, - vx.z - vy.z) + return new_normal3d(self.x - v.x, + self.y - v.y, + self.z - v.z) else: return NotImplemented - def __mul__(object x, object y): - """Multiply operator.""" + def __rsub__(self, object x): + """Reverse subtract operator.""" - cdef double s - cdef Normal3D v - cdef AffineMatrix3D m, minv + cdef _Vec3 v + + if isinstance(x, _Vec3): + + v = <_Vec3>x + + return new_normal3d(v.x - self.x, + v.y - self.y, + v.z - self.z) - if isinstance(x, numbers.Real) and isinstance(y, Normal3D): + else: + + return NotImplemented - s = x - v = y + def __mul__(self, object y): + """Multiply operator.""" - return new_normal3d(s * v.x, - s * v.y, - s * v.z) + cdef double s + cdef AffineMatrix3D m, minv - elif isinstance(x, Normal3D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): s = y - v = x - return new_normal3d(s * v.x, - s * v.y, - s * v.z) + return new_normal3d(s * self.x, + s * self.y, + s * self.z) - elif isinstance(x, AffineMatrix3D) and isinstance(y, Normal3D): + elif isinstance(y, AffineMatrix3D): - m = x - v = y + m = y minv = m.inverse() - return new_normal3d(minv.m[0][0] * v.x + minv.m[1][0] * v.y + minv.m[2][0] * v.z, - minv.m[0][1] * v.x + minv.m[1][1] * v.y + minv.m[2][1] * v.z, - minv.m[0][2] * v.x + minv.m[1][2] * v.y + minv.m[2][2] * v.z) + return new_normal3d(minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, + minv.m[0][1] * self.x + minv.m[1][1] * self.y + minv.m[2][1] * self.z, + minv.m[0][2] * self.x + minv.m[1][2] * self.y + minv.m[2][2] * self.z) else: return NotImplemented @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator.""" cdef double d - cdef Normal3D v - if isinstance(x, Normal3D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y @@ -184,12 +191,11 @@ cdef class Normal3D(_Vec3): raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") - v = x d = 1.0 / d - return new_normal3d(d * v.x, - d * v.y, - d * v.z) + return new_normal3d(d * self.x, + d * self.y, + d * self.z) else: diff --git a/raysect/core/math/point.pyx b/raysect/core/math/point.pyx index 7ebdc2b2..ba4280d6 100644 --- a/raysect/core/math/point.pyx +++ b/raysect/core/math/point.pyx @@ -136,73 +136,66 @@ cdef class Point3D: yield self.y yield self.z - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Point3D(1, 0, 0) + Vector3D(0, 1, 0) Point3D(1.0, 1.0, 0.0) """ - cdef Point3D p cdef _Vec3 v - if isinstance(x, Point3D) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - p = x v = <_Vec3>y else: return NotImplemented - return new_point3d(p.x + v.x, - p.y + v.y, - p.z + v.z) + return new_point3d(self.x + v.x, + self.y + v.y, + self.z + v.z) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Point3D(1, 0, 0) - Vector3D(0, 1, 0) Point3D(1.0, -1.0, 0.0) """ - cdef Point3D p cdef _Vec3 v - if isinstance(x, Point3D) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - p = x v = <_Vec3>y - return new_point3d(p.x - v.x, - p.y - v.y, - p.z - v.z) + return new_point3d(self.x - v.x, + self.y - v.y, + self.z - v.z) else: return NotImplemented @cython.cdivision(True) - def __mul__(object x, object y): + def __rmul__(self, object x): """Multiplication operator. :param AffineMatrix3D x: transformation matrix x - :param Point3D y: point to transform :return: Matrix multiplication of a 3D transformation matrix with the input point. :rtype: Point3D """ cdef AffineMatrix3D m - cdef Point3D v cdef double w - if isinstance(x, AffineMatrix3D) and isinstance(y, Point3D): + if isinstance(x, AffineMatrix3D): m = x - v = y # 4th element of homogeneous coordinate - w = m.m[3][0] * v.x + m.m[3][1] * v.y + m.m[3][2] * v.z + m.m[3][3] + w = m.m[3][0] * self.x + m.m[3][1] * self.y + m.m[3][2] * self.z + m.m[3][3] if w == 0.0: raise ZeroDivisionError("Bad matrix transform, 4th element of homogeneous coordinate is zero.") @@ -210,9 +203,9 @@ cdef class Point3D: # pre divide for speed (dividing is much slower than multiplying) w = 1.0 / w - return new_point3d((m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z + m.m[0][3]) * w, - (m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z + m.m[1][3]) * w, - (m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z + m.m[2][3]) * w) + return new_point3d((m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, + (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, + (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w) return NotImplemented @@ -472,64 +465,58 @@ cdef class Point2D: yield self.x yield self.y - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Point2D(1, 0) + Vector2D(0, 1) Point2D(1.0, 1.0) """ - cdef Point2D p cdef Vector2D v - if isinstance(x, Point2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - p = x v = y else: return NotImplemented - return new_point2d(p.x + v.x, p.y + v.y) + return new_point2d(self.x + v.x, self.y + v.y) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Point2D(1, 0) - Vector2D(0, 1) Point2D(1.0, -1.0) """ - cdef Point2D p cdef Vector2D v - if isinstance(x, Point2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - p = x v = y - return new_point2d(p.x - v.x, p.y - v.y) + return new_point2d(self.x - v.x, self.y - v.y) else: return NotImplemented @cython.cdivision(True) - def __mul__(object x, object y): + def __rmul__(self, object x): """Multiply operator.""" raise NotImplemented # cdef AffineMatrix3D m - # cdef Point3D v # cdef double w # - # if isinstance(x, AffineMatrix3D) and isinstance(y, Point3D): + # if isinstance(x, AffineMatrix3D): # # m = x - # v = y # # # 4th element of homogeneous coordinate - # w = m.m[3][0] * v.x + m.m[3][1] * v.y + m.m[3][2] * v.z + m.m[3][3] + # w = m.m[3][0] * self.x + m.m[3][1] * self.y + m.m[3][2] * self.z + m.m[3][3] # if w == 0.0: # # raise ZeroDivisionError("Bad matrix transform, 4th element of homogeneous coordinate is zero.") @@ -537,9 +524,9 @@ cdef class Point2D: # # pre divide for speed (dividing is much slower than multiplying) # w = 1.0 / w # - # return new_point3d((m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z + m.m[0][3]) * w, - # (m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z + m.m[1][3]) * w, - # (m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z + m.m[2][3]) * w) + # return new_point3d((m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, + # (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, + # (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w) # # return NotImplemented diff --git a/raysect/core/math/quaternion.pyx b/raysect/core/math/quaternion.pyx index 76038a78..51f8a755 100644 --- a/raysect/core/math/quaternion.pyx +++ b/raysect/core/math/quaternion.pyx @@ -127,7 +127,7 @@ cdef class Quaternion: return new_quaternion(-self.x, -self.y, -self.z, -self.s) - def __eq__(object x, object y): + def __eq__(self, object y): """ Equality operator. @@ -137,18 +137,17 @@ cdef class Quaternion: True """ - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, Quaternion): + if isinstance(y, Quaternion): - q1 = x - q2 = y - return q1.x == q2.x and q1.y == q2.y and q1.z == q2.z and q1.s == q2.s + q = y + return self.x == q.x and self.y == q.y and self.z == q.z and self.s == q.s else: raise TypeError('A quaternion can only be equality tested against another quaternion.') - def __add__(object x, object y): + def __add__(self, object y): """ Addition operator. @@ -158,18 +157,17 @@ cdef class Quaternion: Quaternion(0.0, 1.0, 0.0, 1.0) """ - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, Quaternion): + if isinstance(y, Quaternion): - q1 = x - q2 = y - return new_quaternion(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.s + q2.s) + q = y + return new_quaternion(self.x + q.x, self.y + q.y, self.z + q.z, self.s + q.s) else: return NotImplemented - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. .. code-block:: pycon @@ -178,18 +176,17 @@ cdef class Quaternion: Quaternion(0.0, -1.0, 0, 1.0) """ - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, Quaternion): + if isinstance(y, Quaternion): - q1 = x - q2 = y - return new_quaternion(q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.s - q2.s) + q = y + return new_quaternion(self.x - q.x, self.y - q.y, self.z - q.z, self.s - q.s) else: return NotImplemented - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. .. code-block:: pycon @@ -201,31 +198,34 @@ cdef class Quaternion: """ cdef double s - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, numbers.Real) and isinstance(y, Quaternion): + if isinstance(y, numbers.Real): - s = x - q1 = y - return q1.mul_scalar(s) - - elif isinstance(x, Quaternion) and isinstance(y, numbers.Real): - - q1 = x s = y - return q1.mul_scalar(s) + return self.mul_scalar(s) - elif isinstance(x, Quaternion) and isinstance(y, Quaternion): + elif isinstance(y, Quaternion): - q1 = x - q2 = y - return q1.mul_quaternion(q2) + q = y + return self.mul_quaternion(q) else: return NotImplemented() + def __rmul__(self, x): + """Reverse multiplication operator. + + .. code-block:: pycon + + >>> 2 * Quaternion(0, 0, 1, 1) + Quaternion(0.0, 0.0, 2.0, 2.0) + """ + + return self.__mul__(x) + @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator. .. code-block:: pycon @@ -237,19 +237,17 @@ cdef class Quaternion: """ cdef double d - cdef Quaternion q1, q2, q2_inv + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y - q1 = x - return q1.div_scalar(d) + return self.div_scalar(d) - elif isinstance(x, Quaternion) and isinstance(y, Quaternion): + elif isinstance(y, Quaternion): - q1 = x - q2 = y - return q1.div_quaternion(q2) + q = y + return self.div_quaternion(q) else: raise TypeError('Unsupported operand type. Expects a real number.') diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index 08e88a90..2571548a 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -147,99 +147,141 @@ cdef class Vector3D(_Vec3): -self.y, -self.z) - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Vector3D(1, 0, 0) + Vector3D(0, 1, 0) Vector3D(1.0, 1.0, 0.0) """ - cdef _Vec3 vx, vy + cdef _Vec3 vy - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x vy = <_Vec3>y - return new_vector3d(vx.x + vy.x, - vx.y + vy.y, - vx.z + vy.z) + return new_vector3d(self.x + vy.x, + self.y + vy.y, + self.z + vy.z) else: return NotImplemented + + def __radd__(self, object x): + """ Reverse addition operator. + + >>> Vector3D(1, 0, 0) + Vector3D(0, 1, 0) + Vector3D(1.0, 1.0, 0.0) + """ + + return self.__add__(x) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Vector3D(1, 0, 0) - Vector3D(0, 1, 0) Vector3D(1.0, -1.0, 0.0) """ - cdef _Vec3 vx, vy + cdef _Vec3 vy - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x vy = <_Vec3>y - return new_vector3d(vx.x - vy.x, - vx.y - vy.y, - vx.z - vy.z) + return new_vector3d(self.x - vy.x, + self.y - vy.y, + self.z - vy.z) + + else: + + return NotImplemented + + def __rsub__(self, object x): + """ Reverse subtraction operator. + + >>> Vector3D(1, 0, 0) - Vector3D(0, 1, 0) + Vector3D(1.0, -1.0, 0.0) + """ + + cdef _Vec3 vx + + if isinstance(x, _Vec3): + + vx = <_Vec3>x + + return new_vector3d(vx.x - self.x, + vx.y - self.y, + vx.z - self.z) else: return NotImplemented - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. 3D vectors can be multiplied with both scalars and transformation matrices. >>> from raysect.core import Vector3D, rotate_x - >>> 2 * Vector3D(1, 2, 3) + >>> Vector3D(1, 2, 3) * 2 Vector3D(2.0, 4.0, 6.0) - >>> rotate_x(90) * Vector3D(0, 0, 1) - Vector3D(0.0, -1.0, 0.0) """ cdef double s - cdef Vector3D v cdef AffineMatrix3D m - if isinstance(x, numbers.Real) and isinstance(y, Vector3D): + if isinstance(y, numbers.Real): - s = x - v = y + s = y - return new_vector3d(s * v.x, - s * v.y, - s * v.z) + return new_vector3d(s * self.x, + s * self.y, + s * self.z) - elif isinstance(x, Vector3D) and isinstance(y, numbers.Real): + else: - s = y - v = x + return NotImplemented + + def __rmul__(self, object x): + """ Reverse multiplication operator. + + 3D vectors can be multiplied with both scalars and transformation matrices. - return new_vector3d(s * v.x, - s * v.y, - s * v.z) + >>> from raysect.core import Vector3D, rotate_x + >>> 2 * Vector3D(1, 2, 3) + Vector3D(2.0, 4.0, 6.0) + >>> rotate_x(90) * Vector3D(0, 0, 1) + Vector3D(0.0, -1.0, 0.0) + """ - elif isinstance(x, AffineMatrix3D) and isinstance(y, Vector3D): + cdef double s + cdef AffineMatrix3D m - m = x - v = y + if isinstance(x, numbers.Real): - return new_vector3d(m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z, - m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z, - m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z) + s = x - else: + return new_vector3d(s * self.x, + s * self.y, + s * self.z) + + elif isinstance(x, AffineMatrix3D): + + m = x + + return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, + m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, + m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) + + else: + + return NotImplemented - return NotImplemented @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator. >>> Vector3D(1, 1, 1) / 2 @@ -247,9 +289,8 @@ cdef class Vector3D(_Vec3): """ cdef double d - cdef Vector3D v - if isinstance(x, Vector3D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y @@ -258,12 +299,11 @@ cdef class Vector3D(_Vec3): raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") - v = x d = 1.0 / d - return new_vector3d(d * v.x, - d * v.y, - d * v.z) + return new_vector3d(d * self.x, + d * self.y, + d * self.z) else: @@ -705,87 +745,92 @@ cdef class Vector2D: return new_vector2d(-self.x, -self.y) - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Vector2D(1, 0) + Vector2D(0, 1) Vector2D(1.0, 1.0) """ - cdef Vector2D vx, vy + cdef Vector2D vy - if isinstance(x, Vector2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - vx = x vy = y - return new_vector2d(vx.x + vy.x, vx.y + vy.y) + return new_vector2d(self.x + vy.x, self.y + vy.y) else: return NotImplemented - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Vector2D(1, 0) - Vector2D(0, 1) Vector2D(1.0, -1.0) """ - cdef Vector2D vx, vy + cdef Vector2D vy - if isinstance(x, Vector2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - vx = x vy = y - return new_vector2d(vx.x - vy.x, vx.y - vy.y) + return new_vector2d(self.x - vy.x, self.y - vy.y) else: return NotImplemented - # TODO - add 2D affine transformations - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. - >>> 2 * Vector3D(1, 2) + >>> Vector3D(1, 2) * 2 Vector2D(2.0, 4.0) """ cdef double s - cdef Vector2D v # cdef AffineMatrix2D m - if isinstance(x, numbers.Real) and isinstance(y, Vector2D): + if isinstance(y, numbers.Real): - s = x - v = y + s = y - return new_vector2d(s * v.x, s * v.y,) + return new_vector2d(s * self.x, s * self.y) - elif isinstance(x, Vector2D) and isinstance(y, numbers.Real): + else: - s = y - v = x + return NotImplemented + + # TODO - add 2D affine transformations + def __rmul__(self, object x): + """Reverse multiplication operator. + + >>> 2 * Vector3D(1, 2) + Vector2D(2.0, 4.0) + """ - return new_vector2d(s * v.x, s * v.y) + cdef double s + # cdef AffineMatrix2D m + + if isinstance(x, numbers.Real): + + s = x - # elif isinstance(x, AffineMatrix3D) and isinstance(y, Vector3D): + return new_vector2d(s * self.x, s * self.y) + + # elif isinstance(x, AffineMatrix2D): # - # m = x - # v = y + # m = x # - # return new_vector3d(m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z, - # m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z, - # m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z) + # return new_vector2d(m.m[0][0] * self.x + m.m[0][1] * self.y, + # m.m[1][0] * self.x + m.m[1][1] * self.y) - else: - return NotImplemented @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator. >>> Vector2D(1, 1) / 2 @@ -793,9 +838,8 @@ cdef class Vector2D: """ cdef double d - cdef Vector2D v - if isinstance(x, Vector2D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y @@ -804,10 +848,9 @@ cdef class Vector2D: raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") - v = x d = 1.0 / d - return new_vector2d(d * v.x, d * v.y) + return new_vector2d(d * self.x, d * self.y) else: From 4095c99331a2f03ab27fbe74ef480e3bc5bf8346 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 28 Jul 2023 18:06:45 +0900 Subject: [PATCH 02/11] fix inconsistent indentation --- raysect/core/math/vector.pyx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index 2571548a..d05539bd 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -266,18 +266,18 @@ cdef class Vector3D(_Vec3): return new_vector3d(s * self.x, s * self.y, s * self.z) - + elif isinstance(x, AffineMatrix3D): - - m = x - - return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, - m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, - m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) - - else: - - return NotImplemented + + m = x + + return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, + m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, + m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) + + else: + + return NotImplemented @cython.cdivision(True) From 2d1a680d0fe7f2f7ebf898438135d035730c528d Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 28 Jul 2023 18:51:24 +0900 Subject: [PATCH 03/11] fix binary arithmetic operators in `math` module - mainly function base modules are modified --- raysect/core/math/affinematrix.pyx | 6 +- .../math/function/float/function2d/base.pyx | 159 ++++++++++-------- .../math/function/float/function3d/base.pyx | 159 ++++++++++-------- .../function/vector3d/function1d/base.pyx | 41 ++--- .../function/vector3d/function2d/base.pyx | 41 ++--- .../function/vector3d/function3d/base.pyx | 39 +++-- raysect/core/math/normal.pyx | 23 ++- 7 files changed, 264 insertions(+), 204 deletions(-) diff --git a/raysect/core/math/affinematrix.pyx b/raysect/core/math/affinematrix.pyx index ff729f75..33c3413d 100644 --- a/raysect/core/math/affinematrix.pyx +++ b/raysect/core/math/affinematrix.pyx @@ -133,7 +133,7 @@ cdef class AffineMatrix3D(_Mat4): s += ", " return s + "])" - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. >>> from raysect.core import translate, rotate_x @@ -146,9 +146,9 @@ cdef class AffineMatrix3D(_Mat4): cdef AffineMatrix3D mx, my - if isinstance(x, AffineMatrix3D) and isinstance(y, AffineMatrix3D): + if isinstance(y, AffineMatrix3D): - mx = x + mx = self my = y return new_affinematrix3d(mx.m[0][0] * my.m[0][0] + mx.m[0][1] * my.m[1][0] + mx.m[0][2] * my.m[2][0] + mx.m[0][3] * my.m[3][0], mx.m[0][0] * my.m[0][1] + mx.m[0][1] * my.m[1][1] + mx.m[0][2] * my.m[2][1] + mx.m[0][3] * my.m[3][1], diff --git a/raysect/core/math/function/float/function2d/base.pyx b/raysect/core/math/function/float/function2d/base.pyx index f8d80567..2661ab86 100644 --- a/raysect/core/math/function/float/function2d/base.pyx +++ b/raysect/core/math/function/float/function2d/base.pyx @@ -63,104 +63,121 @@ cdef class Function2D(FloatFunction): """ return self.evaluate(x, y) - def __add__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() + b() - return AddFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() + B -> B + a() - return AddScalar2D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A + b() - return AddScalar2D( a, b) + def __add__(self, object b): + if is_callable(b): + # a() + b() + return AddFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() + B -> B + a() + return AddScalar2D( b, self) return NotImplemented - def __sub__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() - b() - return SubtractFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() - B -> -B + a() - return AddScalar2D(-( b), a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A - b() - return SubtractScalar2D( a, b) + def __radd__(self, object a): + return self.__add__(a) + + def __sub__(self, object b): + if is_callable(b): + # a() - b() + return SubtractFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() - B -> -B + a() + return AddScalar2D(-( b), self) return NotImplemented - def __mul__(object a, object b): + def __rsub__(self, object a): if is_callable(a): - if is_callable(b): - # a() * b() - return MultiplyFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() * B -> B * a() - return MultiplyScalar2D( b, a) + # a() - b() + return SubtractFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return MultiplyScalar2D( a, b) + # A - b() + return SubtractScalar2D( a, self) return NotImplemented + def __mul__(self, object b): + if is_callable(b): + # a() * b() + return MultiplyFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() * B -> B * a() + return MultiplyScalar2D( b, self) + return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) + @cython.cdivision(True) - def __truediv__(object a, object b): + def __truediv__(self, object b): cdef double v + if is_callable(b): + # a() / b() + return DivideFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() / B -> 1/B * a() + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") + return MultiplyScalar2D(1/v, self) + return NotImplemented + + @cython.cdivision(True) + def __rtruediv__(self, object a): if is_callable(a): - if is_callable(b): - # a() / b() - return DivideFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() / B -> 1/B * a() - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") - return MultiplyScalar2D(1/v, a) + # a() / b() + return DivideFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return DivideScalar2D( a, b) + # A / b() + return DivideScalar2D( a, self) return NotImplemented - def __mod__(object a, object b): + def __mod__(self, object b): cdef double v + if is_callable(b): + # a() % b() + return ModuloFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() % B + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") + return ModuloFunctionScalar2D(self, v) + return NotImplemented + + def __rmod__(self, object a): if is_callable(a): - if is_callable(b): - # a() % b() - return ModuloFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() % B - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") - return ModuloFunctionScalar2D(a, v) + # a() % b() + return ModuloFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A % b() - return ModuloScalarFunction2D( a, b) + # A % b() + return ModuloScalarFunction2D( a, self) return NotImplemented def __neg__(self): return MultiplyScalar2D(-1, self) - def __pow__(object a, object b, object c): + def __pow__(self, object b, object c): + if c is not None: + # Optimised implementation of pow(a, b, c) not available: fall back + # to general implementation + return PowFunction2D(self, b) % c + if is_callable(b): + # a() ** b() + return PowFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() ** b + return PowFunctionScalar2D(self, b) + return NotImplemented + + def __rpow__(self, object a, object c): if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation - return (a ** b) % c + return PowFunction2D(a, self) % c if is_callable(a): - if is_callable(b): - # a() ** b() - return PowFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() ** b - return PowFunctionScalar2D(a, b) + # a() ** b() + return PowFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # a ** b() - return PowScalarFunction2D( a, b) + # A ** b() + return PowScalarFunction2D( a, self) return NotImplemented def __abs__(self): diff --git a/raysect/core/math/function/float/function3d/base.pyx b/raysect/core/math/function/float/function3d/base.pyx index b5f1933f..36310a8b 100644 --- a/raysect/core/math/function/float/function3d/base.pyx +++ b/raysect/core/math/function/float/function3d/base.pyx @@ -64,104 +64,121 @@ cdef class Function3D(FloatFunction): """ return self.evaluate(x, y, z) - def __add__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() + b() - return AddFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() + B -> B + a() - return AddScalar3D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A + b() - return AddScalar3D( a, b) + def __add__(self, object b): + if is_callable(b): + # a() + b() + return AddFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() + B -> B + a() + return AddScalar3D( b, self) return NotImplemented - def __sub__(object a, object b): + def __radd__(self, object a): + return self.__add__(a) + + def __sub__(self, object b): + if is_callable(b): + # a() - b() + return SubtractFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() - B -> -B + a() + return AddScalar3D(-( b), self) + return NotImplemented + + def __rsub__(self, object a): if is_callable(a): - if is_callable(b): - # a() - b() - return SubtractFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() - B -> -B + a() - return AddScalar3D(-( b), a) + # a() - b() + return SubtractFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A - b() - return SubtractScalar3D( a, b) + # A - b() + return SubtractScalar3D( a, self) return NotImplemented - def __mul__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() * b() - return MultiplyFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() * B -> B * a() - return MultiplyScalar3D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return MultiplyScalar3D( a, b) + def __mul__(self, object b): + if is_callable(b): + # a() * b() + return MultiplyFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() * B -> B * a() + return MultiplyScalar3D( b, self) return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) @cython.cdivision(True) - def __truediv__(object a, object b): + def __truediv__(self, object b): cdef double v + if is_callable(b): + # a() / b() + return DivideFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() / B -> 1/B * a() + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") + return MultiplyScalar3D(1/v, self) + return NotImplemented + + @cython.cdivision(True) + def __rtruediv__(self, object a): if is_callable(a): - if is_callable(b): - # a() / b() - return DivideFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() / B -> 1/B * a() - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") - return MultiplyScalar3D(1/v, a) + # a() / b() + return DivideFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return DivideScalar3D( a, b) + # A / b() + return DivideScalar3D( a, self) return NotImplemented - def __mod__(object a, object b): + def __mod__(self, object b): cdef double v + if is_callable(b): + # a() % b() + return ModuloFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() % B + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") + return ModuloFunctionScalar3D(self, v) + return NotImplemented + + def __rmod__(self, object a): if is_callable(a): - if is_callable(b): - # a() % b() - return ModuloFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() % B - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") - return ModuloFunctionScalar3D(a, v) + # a() % b() + return ModuloFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A % b() - return ModuloScalarFunction3D( a, b) + # A % b() + return ModuloScalarFunction3D( a, self) return NotImplemented def __neg__(self): return MultiplyScalar3D(-1, self) - def __pow__(object a, object b, object c): + def __pow__(self, object b, object c): + if c is not None: + # Optimised implementation of pow(a, b, c) not available: fall back + # to general implementation + return PowFunction3D(self, b) % c + if is_callable(b): + # a() ** b() + return PowFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() ** b + return PowFunctionScalar3D(self, b) + return NotImplemented + + def __rpow__(self, object a, object c): if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation - return (a ** b) % c + return PowFunction3D(a, self) % c if is_callable(a): - if is_callable(b): - # a() ** b() - return PowFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() ** b - return PowFunctionScalar3D(a, b) + # a() ** b() + return PowFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # a ** b() - return PowScalarFunction3D( a, b) + # A ** b() + return PowScalarFunction3D( a, self) return NotImplemented def __abs__(self): diff --git a/raysect/core/math/function/vector3d/function1d/base.pyx b/raysect/core/math/function/vector3d/function1d/base.pyx index 2cc9942b..eb34070f 100644 --- a/raysect/core/math/function/vector3d/function1d/base.pyx +++ b/raysect/core/math/function/vector3d/function1d/base.pyx @@ -65,31 +65,34 @@ cdef class Function1D(Vector3DFunction): """ return self.evaluate(x) - def __add__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return AddFunction1D(a, b) + def __add__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return AddFunction1D(self, b) return NotImplemented + + def __radd__(self, object a): + return self.__add__(a) - def __sub__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return SubtractFunction1D(a, b) + def __sub__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return SubtractFunction1D(self, b) return NotImplemented - - def __mul__(object a, object b): + + def __rsub__(self, object a): if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return MultiplyFunction1D(a, b) - if is_callable(b) or isinstance(b, Vector3D): - if float_is_callable(a) or isinstance(a, numbers.Real): - return MultiplyFunction1D(b, a) + return SubtractFunction1D(a, self) + + def __mul__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return MultiplyFunction1D(self, b) return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) - def __truediv__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return DivideFunction1D(a, b) + def __truediv__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return DivideFunction1D(self, b) return NotImplemented def __neg__(self): diff --git a/raysect/core/math/function/vector3d/function2d/base.pyx b/raysect/core/math/function/vector3d/function2d/base.pyx index 6f3f4ff1..3d578155 100644 --- a/raysect/core/math/function/vector3d/function2d/base.pyx +++ b/raysect/core/math/function/vector3d/function2d/base.pyx @@ -66,31 +66,34 @@ cdef class Function2D(Vector3DFunction): """ return self.evaluate(x, y) - def __add__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return AddFunction2D(a, b) + def __add__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return AddFunction2D(self, b) return NotImplemented - def __sub__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return SubtractFunction2D(a, b) - return NotImplemented + def __radd__(self, object a): + return self.__add__(a) - def __mul__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return MultiplyFunction2D(a, b) + def __sub__(self, object b): if is_callable(b) or isinstance(b, Vector3D): - if float_is_callable(a) or isinstance(a, numbers.Real): - return MultiplyFunction2D(b, a) + return SubtractFunction2D(self, b) return NotImplemented - - def __truediv__(object a, object b): + + def __rsub__(self, object a): if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return DivideFunction2D(a, b) + return SubtractFunction2D(a, self) + + def __mul__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return MultiplyFunction2D(self, b) + return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) + + def __truediv__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return DivideFunction2D(self, b) return NotImplemented def __neg__(self): diff --git a/raysect/core/math/function/vector3d/function3d/base.pyx b/raysect/core/math/function/vector3d/function3d/base.pyx index 79242d5d..7ae4e03b 100644 --- a/raysect/core/math/function/vector3d/function3d/base.pyx +++ b/raysect/core/math/function/vector3d/function3d/base.pyx @@ -66,31 +66,34 @@ cdef class Function3D(Vector3DFunction): """ return self.evaluate(x, y, z) - def __add__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return AddFunction3D(a, b) + def __add__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return AddFunction3D(self, b) return NotImplemented - def __sub__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return SubtractFunction3D(a, b) - return NotImplemented + def __radd__(self, object a): + return self.__add__(a) - def __mul__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return MultiplyFunction3D(a, b) + def __sub__(self, object b): if is_callable(b) or isinstance(b, Vector3D): - if float_is_callable(a) or isinstance(a, numbers.Real): - return MultiplyFunction3D(b, a) + return SubtractFunction3D(self, b) return NotImplemented - def __truediv__(object a, object b): + def __rsub__(self, object a): if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return DivideFunction3D(a, b) + return SubtractFunction3D(a, self) + + def __mul__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return MultiplyFunction3D(self, b) + return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) + + def __truediv__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return DivideFunction3D(self, b) return NotImplemented def __neg__(self): diff --git a/raysect/core/math/normal.pyx b/raysect/core/math/normal.pyx index 6334c781..29b29288 100644 --- a/raysect/core/math/normal.pyx +++ b/raysect/core/math/normal.pyx @@ -153,7 +153,6 @@ cdef class Normal3D(_Vec3): """Multiply operator.""" cdef double s - cdef AffineMatrix3D m, minv if isinstance(y, numbers.Real): @@ -163,9 +162,27 @@ cdef class Normal3D(_Vec3): s * self.y, s * self.z) - elif isinstance(y, AffineMatrix3D): + else: + + return NotImplemented + + def __rmul__(self, object x): + """Reverse multiply operator.""" + + cdef double s + cdef AffineMatrix3D m, minv + + if isinstance(x, numbers.Real): + + s = x + + return new_normal3d(s * self.x, + s * self.y, + s * self.z) + + elif isinstance(x, AffineMatrix3D): - m = y + m = x minv = m.inverse() return new_normal3d(minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, From e9800f3ff8a3b9ec7cda2af0dfa4ab5c02f4fe92 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 28 Jul 2023 18:52:34 +0900 Subject: [PATCH 04/11] add `noexcept` to `_edge_compare` function this modification is derived from the cython 3.0 compile error. --- raysect/core/math/spatial/kdtree2d.pyx | 2 +- raysect/core/math/spatial/kdtree3d.pyx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raysect/core/math/spatial/kdtree2d.pyx b/raysect/core/math/spatial/kdtree2d.pyx index 2e26888e..80f6224c 100644 --- a/raysect/core/math/spatial/kdtree2d.pyx +++ b/raysect/core/math/spatial/kdtree2d.pyx @@ -74,7 +74,7 @@ cdef class Item2D: self.box = box -cdef int _edge_compare(const void *p1, const void *p2) nogil: +cdef int _edge_compare(const void *p1, const void *p2) noexcept nogil: cdef edge e1, e2 diff --git a/raysect/core/math/spatial/kdtree3d.pyx b/raysect/core/math/spatial/kdtree3d.pyx index e82306f4..e5901329 100644 --- a/raysect/core/math/spatial/kdtree3d.pyx +++ b/raysect/core/math/spatial/kdtree3d.pyx @@ -75,7 +75,7 @@ cdef class Item3D: self.box = box -cdef int _edge_compare(const void *p1, const void *p2) nogil: +cdef int _edge_compare(const void *p1, const void *p2) noexcept nogil: cdef edge e1, e2 From 245faf7153c3842482b7586f162d731d78d98389 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Wed, 2 Aug 2023 16:39:24 +0900 Subject: [PATCH 05/11] add ignoring `numpy` deplicated warning --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ed029aa6..38b04f76 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ # 'auto_pickle': True, 'language_level': 3 } +macros = [("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")] setup_path = path.dirname(path.abspath(__file__)) if line_profile: @@ -59,7 +60,7 @@ if path.splitext(file)[1] == ".pyx": pyx_file = path.relpath(path.join(root, file), setup_path) module = path.splitext(pyx_file)[0].replace("/", ".") - extensions.append(Extension(module, [pyx_file], include_dirs=compilation_includes, extra_compile_args=compilation_args),) + extensions.append(Extension(module, [pyx_file], include_dirs=compilation_includes, extra_compile_args=compilation_args, define_macros=macros),) if profile: cython_directives["profile"] = True @@ -77,7 +78,7 @@ if path.splitext(file)[1] == ".c": c_file = path.relpath(path.join(root, file), setup_path) module = path.splitext(c_file)[0].replace("/", ".") - extensions.append(Extension(module, [c_file], include_dirs=compilation_includes, extra_compile_args=compilation_args),) + extensions.append(Extension(module, [c_file], include_dirs=compilation_includes, extra_compile_args=compilation_args, define_macros=macros),) # parse the package version number with open(path.join(path.dirname(__file__), 'raysect/VERSION')) as version_file: From b993ac6e213b04b60801101e1ac0ba841171eeb0 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Thu, 27 Jul 2023 21:30:35 +0900 Subject: [PATCH 06/11] change to return from raise --- raysect/core/math/point.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raysect/core/math/point.pyx b/raysect/core/math/point.pyx index ba4280d6..8d95693b 100644 --- a/raysect/core/math/point.pyx +++ b/raysect/core/math/point.pyx @@ -506,7 +506,7 @@ cdef class Point2D: @cython.cdivision(True) def __rmul__(self, object x): """Multiply operator.""" - raise NotImplemented + return NotImplemented # cdef AffineMatrix3D m # cdef double w From 7a12a044135d0fbcfca1bf5e2ea2449428b9f4c6 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Wed, 27 Nov 2024 11:22:57 +0100 Subject: [PATCH 07/11] update cython version constraint in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1fcde71..a917a861 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython>=0.28"] +requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython~=3.0"] build-backend = "setuptools.build_meta" From 80a6f5f7d9cf698e12e999dc1a6a11786a5bbde7 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:06 -0400 Subject: [PATCH 08/11] fix: replace `DEF` statements with `cdef enum`, `cdef const` and use `INFINITY` in `libc.math` . --- raysect/core/boundingbox.pyx | 13 ++-- .../float/function2d/interpolate/common.pyx | 13 ++-- .../float/function3d/interpolate/common.pyx | 19 +++--- raysect/core/math/quaternion.pyx | 20 +++--- raysect/core/math/random.pyx | 5 +- raysect/core/math/sampler/solidangle.pyx | 9 ++- raysect/core/math/spatial/kdtree2d.pyx | 17 ++--- raysect/core/math/spatial/kdtree3d.pyx | 23 +++---- raysect/core/math/transform.pyx | 5 +- raysect/core/math/vector.pyx | 22 ++----- raysect/core/ray.pyx | 4 +- raysect/optical/loggingray.pyx | 5 +- .../optical/material/modifiers/roughen.pyx | 3 +- .../observer/imaging/targetted_ccd.pyx | 2 +- .../observer/nonimaging/fibreoptic.pyx | 2 +- .../observer/nonimaging/mesh_camera.pyx | 15 +++-- .../observer/nonimaging/mesh_pixel.pyx | 19 +++--- .../observer/nonimaging/targetted_pixel.pyx | 2 +- raysect/optical/ray.pyx | 5 +- raysect/optical/spectrum.pyx | 2 +- raysect/primitive/box.pyx | 32 +++++----- raysect/primitive/cone.pyx | 16 ++--- raysect/primitive/csg.pyx | 9 ++- raysect/primitive/cylinder.pyx | 30 ++++----- raysect/primitive/lens/spherical.pyx | 10 +-- raysect/primitive/mesh/mesh.pyx | 64 +++++++++---------- raysect/primitive/parabola.pyx | 16 ++--- raysect/primitive/sphere.pyx | 6 +- 28 files changed, 184 insertions(+), 204 deletions(-) diff --git a/raysect/core/boundingbox.pyx b/raysect/core/boundingbox.pyx index eb616121..304597d3 100644 --- a/raysect/core/boundingbox.pyx +++ b/raysect/core/boundingbox.pyx @@ -32,18 +32,17 @@ # TODO: add docstrings cimport cython +from libc.math cimport INFINITY from raysect.core.math cimport new_point3d, new_point2d -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # axis defines -DEF X_AXIS = 0 -DEF Y_AXIS = 1 -DEF Z_AXIS = 2 +cdef enum: + X_AXIS = 0 + Y_AXIS = 1 + Z_AXIS = 2 # defines the padding on the sphere which encloses the BoundingBox3D. -DEF SPHERE_PADDING = 1.000001 +cdef const double SPHERE_PADDING = 1.000001 @cython.freelist(256) diff --git a/raysect/core/math/function/float/function2d/interpolate/common.pyx b/raysect/core/math/function/float/function2d/interpolate/common.pyx index ddfb68f7..a20ca9f8 100644 --- a/raysect/core/math/function/float/function2d/interpolate/common.pyx +++ b/raysect/core/math/function/float/function2d/interpolate/common.pyx @@ -38,15 +38,16 @@ from raysect.core.math.cython cimport barycentric_inside_triangle, barycentric_c cimport cython # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-6 +cdef const double BOX_PADDING = 1e-6 # convenience defines -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 +cdef enum: + V1 = 0 + V2 = 1 + V3 = 2 -DEF X = 0 -DEF Y = 1 + X = 0 + Y = 1 cdef class MeshKDTree2D(KDTree2DCore): diff --git a/raysect/core/math/function/float/function3d/interpolate/common.pyx b/raysect/core/math/function/float/function3d/interpolate/common.pyx index dee3cc4e..40976828 100644 --- a/raysect/core/math/function/float/function3d/interpolate/common.pyx +++ b/raysect/core/math/function/float/function3d/interpolate/common.pyx @@ -38,17 +38,18 @@ from raysect.core.math.cython cimport barycentric_inside_tetrahedra, barycentric cimport cython # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-6 +cdef const double BOX_PADDING = 1e-6 # convenience defines -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 -DEF V4 = 3 - -DEF X = 0 -DEF Y = 1 -DEF Z = 2 +cdef enum: + V1 = 0 + V2 = 1 + V3 = 2 + V4 = 3 + + X = 0 + Y = 1 + Z = 2 cdef class MeshKDTree3D(KDTree3DCore): diff --git a/raysect/core/math/quaternion.pyx b/raysect/core/math/quaternion.pyx index 51f8a755..210c2a7d 100644 --- a/raysect/core/math/quaternion.pyx +++ b/raysect/core/math/quaternion.pyx @@ -36,8 +36,8 @@ from libc.math cimport sqrt, sin, cos, asin, acos, atan2, fabs, M_PI, copysign from raysect.core.math.vector cimport new_vector3d from raysect.core.math.affinematrix cimport new_affinematrix3d, AffineMatrix3D -DEF RAD2DEG = 57.29577951308232000 # 180 / pi -DEF DEG2RAD = 0.017453292519943295 # pi / 180 +cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi +cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 cdef class Quaternion: @@ -288,8 +288,8 @@ cdef class Quaternion: cpdef Quaternion conjugate(self): """ - Complex conjugate operator. - + Complex conjugate operator. + .. code-block:: pycon >>> Quaternion(1, 2, 3, 0).conjugate() @@ -320,7 +320,7 @@ cdef class Quaternion: The returned quaternion is normalised to have norm length 1.0 - a unit quaternion. .. code-block:: pycon - + >>> a = Quaternion(1, 2, 3, 0) >>> a.normalise() Quaternion(0.26726, 0.53452, 0.80178, 0.0) @@ -419,12 +419,12 @@ cdef class Quaternion: This method calculates the quaternion required to map this quaternion onto the supplied quaternion. Both quaternions will be normalised and a normalised quaternion will be returned. - + .. code-block:: pycon - + >>> from raysect.core.math import Quaternion >>> - >>> q1 = Quaternion.from_axis_angle(Vector3D(1,0,0), 10) + >>> q1 = Quaternion.from_axis_angle(Vector3D(1,0,0), 10) >>> q2 = Quaternion.from_axis_angle(Vector3D(1,0,0), 25) >>> d = q1.quaternion_to(q2) >>> d @@ -433,9 +433,9 @@ cdef class Quaternion: 15.000000000000027 >>> d.axis Vector3D(1.0, 0.0, 0.0) - + :param Quaternion q: The target quaternion. - :return: A new Quaternion object representing the specified rotation. + :return: A new Quaternion object representing the specified rotation. """ return q.normalise().mul_quaternion(self.normalise().conjugate()).normalise() diff --git a/raysect/core/math/random.pyx b/raysect/core/math/random.pyx index 096acb04..029751a3 100644 --- a/raysect/core/math/random.pyx +++ b/raysect/core/math/random.pyx @@ -96,8 +96,9 @@ from libc.math cimport cos, sin, asin, log, fabs, sqrt, M_PI as PI from libc.stdint cimport uint64_t, int64_t cimport cython -DEF NN = 312 -DEF MM = 156 +cdef enum: + NN = 312 + MM = 156 # The array for the state vector cdef uint64_t mt[NN] diff --git a/raysect/core/math/sampler/solidangle.pyx b/raysect/core/math/sampler/solidangle.pyx index 55a76e33..9546e214 100644 --- a/raysect/core/math/sampler/solidangle.pyx +++ b/raysect/core/math/sampler/solidangle.pyx @@ -34,9 +34,8 @@ from raysect.core.math cimport Vector3D, new_vector3d from raysect.core.math.random cimport uniform # TODO: add tests - idea: solve the lighting equation with a uniform emitting surface with each sampler and check the mean radiance is unity - -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) -DEF R_4_PI = 0.07957747154594767 # 1 / (4 * pi) +cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) +cdef const double R_4_PI = 0.07957747154594767 # 1 / (4 * pi) cdef class SolidAngleSampler: @@ -73,7 +72,7 @@ cdef class SolidAngleSampler: cpdef double pdf(self, Vector3D sample): """ Generates a pdf for a given sample value. - + Vectors *must* be normalised. :param Vector3D sample: The sample point at which to get the pdf. @@ -242,7 +241,7 @@ cdef class ConeUniformSampler(SolidAngleSampler): Generates a uniform weighted random vector from a cone. The cone is aligned along the z-axis. - + :param angle: Angle of the cone in degrees (default=45). .. code-block:: pycon diff --git a/raysect/core/math/spatial/kdtree2d.pyx b/raysect/core/math/spatial/kdtree2d.pyx index 80f6224c..96270c3f 100644 --- a/raysect/core/math/spatial/kdtree2d.pyx +++ b/raysect/core/math/spatial/kdtree2d.pyx @@ -40,16 +40,17 @@ from libc.stdint cimport int32_t from libc.math cimport log, ceil cimport cython -# this number of nodes will be pre-allocated when the kd-tree is initially created -DEF INITIAL_NODE_COUNT = 128 +cdef enum: + # this number of nodes will be pre-allocated when the kd-tree is initially created + INITIAL_NODE_COUNT = 128 -# friendly name for first node -DEF ROOT_NODE = 0 + # friendly name for first node + ROOT_NODE = 0 -# node types -DEF LEAF = -1 # leaf node -DEF X_AXIS = 0 # branch, x-axis split -DEF Y_AXIS = 1 # branch, y-axis split + # node types + LEAF = -1 # leaf node + X_AXIS = 0 # branch, x-axis split + Y_AXIS = 1 # branch, y-axis split cdef class Item2D: diff --git a/raysect/core/math/spatial/kdtree3d.pyx b/raysect/core/math/spatial/kdtree3d.pyx index e5901329..62c0565d 100644 --- a/raysect/core/math/spatial/kdtree3d.pyx +++ b/raysect/core/math/spatial/kdtree3d.pyx @@ -40,17 +40,18 @@ from libc.stdint cimport int32_t from libc.math cimport log, ceil cimport cython -# this number of nodes will be pre-allocated when the kd-tree is initially created -DEF INITIAL_NODE_COUNT = 128 - -# friendly name for first node -DEF ROOT_NODE = 0 - -# node types -DEF LEAF = -1 # leaf node -DEF X_AXIS = 0 # branch, x-axis split -DEF Y_AXIS = 1 # branch, y-axis split -DEF Z_AXIS = 2 # branch, z-axis split +cdef enum: + # this number of nodes will be pre-allocated when the kd-tree is initially created + INITIAL_NODE_COUNT = 128 + + # friendly name for first node + ROOT_NODE = 0 + + # node types + LEAF = -1 # leaf node + X_AXIS = 0 # branch, x-axis split + Y_AXIS = 1 # branch, y-axis split + Z_AXIS = 2 # branch, z-axis split cdef class Item3D: diff --git a/raysect/core/math/transform.pyx b/raysect/core/math/transform.pyx index 75b5b93c..80e157e5 100644 --- a/raysect/core/math/transform.pyx +++ b/raysect/core/math/transform.pyx @@ -35,9 +35,8 @@ from raysect.core.math.point cimport new_point3d cimport cython -DEF RAD2DEG = 57.29577951308232000 # 180 / pi -DEF DEG2RAD = 0.017453292519943295 # pi / 180 - +cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi +cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 FORWARD = 'forward' UP = 'up' diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index d05539bd..13568bd9 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -33,7 +33,7 @@ import numbers cimport cython from libc.math cimport sqrt, fabs, NAN, acos, cos, sin -DEF EPSILON = 1e-12 +cdef const double EPSILON = 1e-12 cdef class Vector3D(_Vec3): @@ -167,7 +167,7 @@ cdef class Vector3D(_Vec3): else: return NotImplemented - + def __radd__(self, object x): """ Reverse addition operator. @@ -197,7 +197,7 @@ cdef class Vector3D(_Vec3): else: return NotImplemented - + def __rsub__(self, object x): """ Reverse subtraction operator. @@ -791,7 +791,6 @@ cdef class Vector2D: """ cdef double s - # cdef AffineMatrix2D m if isinstance(y, numbers.Real): @@ -800,10 +799,8 @@ cdef class Vector2D: return new_vector2d(s * self.x, s * self.y) else: + raise TypeError("Unsupported operand type. Expects a real number.") - return NotImplemented - - # TODO - add 2D affine transformations def __rmul__(self, object x): """Reverse multiplication operator. @@ -812,20 +809,15 @@ cdef class Vector2D: """ cdef double s - # cdef AffineMatrix2D m if isinstance(x, numbers.Real): s = x return new_vector2d(s * self.x, s * self.y) - - # elif isinstance(x, AffineMatrix2D): - # - # m = x - # - # return new_vector2d(m.m[0][0] * self.x + m.m[0][1] * self.y, - # m.m[1][0] * self.x + m.m[1][1] * self.y) + + else: + raise TypeError("Unsupported operand type. Expects a real number.") diff --git a/raysect/core/ray.pyx b/raysect/core/ray.pyx index 15d97910..9ed22008 100644 --- a/raysect/core/ray.pyx +++ b/raysect/core/ray.pyx @@ -29,12 +29,10 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from libc.math cimport INFINITY from raysect.core.math cimport new_point3d cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - @cython.freelist(256) cdef class Ray: diff --git a/raysect/optical/loggingray.pyx b/raysect/optical/loggingray.pyx index 5fa38d5b..cc1cc038 100644 --- a/raysect/optical/loggingray.pyx +++ b/raysect/optical/loggingray.pyx @@ -29,6 +29,8 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from libc.math cimport INFINITY + from raysect.core cimport Intersection, Point3D, Vector3D from raysect.core.math.random cimport probability @@ -39,9 +41,6 @@ from raysect.optical.material.material cimport Material cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - cdef class LoggingRay(Ray): diff --git a/raysect/optical/material/modifiers/roughen.pyx b/raysect/optical/material/modifiers/roughen.pyx index d705b1f7..88cfd5b4 100644 --- a/raysect/optical/material/modifiers/roughen.pyx +++ b/raysect/optical/material/modifiers/roughen.pyx @@ -36,7 +36,8 @@ from raysect.optical.material cimport Material # sets the maximum number of attempts to find a valid perturbed normal # it is highly unlikely (REALLY!) this number will ever be reached, it is just there for my paranoia # in the worst case 50% of the random hemisphere will always generate a valid solution... so P(fail) < 0.5^50! -DEF SAMPLE_ATTEMPTS = 50 +cdef enum: + SAMPLE_ATTEMPTS = 50 cdef HemisphereCosineSampler hemisphere_sampler = HemisphereCosineSampler() diff --git a/raysect/optical/observer/imaging/targetted_ccd.pyx b/raysect/optical/observer/imaging/targetted_ccd.pyx index cb355626..e14aaefb 100644 --- a/raysect/optical/observer/imaging/targetted_ccd.pyx +++ b/raysect/optical/observer/imaging/targetted_ccd.pyx @@ -40,7 +40,7 @@ from raysect.optical.observer.base cimport Observer2D cimport cython -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) +cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) cdef class TargettedCCDArray(Observer2D): diff --git a/raysect/optical/observer/nonimaging/fibreoptic.pyx b/raysect/optical/observer/nonimaging/fibreoptic.pyx index c3fde275..f5592d2e 100644 --- a/raysect/optical/observer/nonimaging/fibreoptic.pyx +++ b/raysect/optical/observer/nonimaging/fibreoptic.pyx @@ -40,7 +40,7 @@ cimport cython # 1 / (2 * PI) -DEF RECIP_2_PI = 0.15915494309189535 +cdef const double RECIP_2_PI = 0.15915494309189535 # TODO - provide a function for angular fall off for collection, instead of acceptance cone. diff --git a/raysect/optical/observer/nonimaging/mesh_camera.pyx b/raysect/optical/observer/nonimaging/mesh_camera.pyx index 79b5aabf..5c2d3adf 100644 --- a/raysect/optical/observer/nonimaging/mesh_camera.pyx +++ b/raysect/optical/observer/nonimaging/mesh_camera.pyx @@ -44,13 +44,14 @@ from raysect.primitive.mesh.mesh cimport Mesh cimport cython # convenience defines -DEF X = 0 -DEF Y = 1 -DEF Z = 2 - -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 +cdef enum: + X = 0 + Y = 1 + Z = 2 + + V1 = 0 + V2 = 1 + V3 = 2 cdef class MeshCamera(Observer1D): diff --git a/raysect/optical/observer/nonimaging/mesh_pixel.pyx b/raysect/optical/observer/nonimaging/mesh_pixel.pyx index 61a7479a..adbcce89 100644 --- a/raysect/optical/observer/nonimaging/mesh_pixel.pyx +++ b/raysect/optical/observer/nonimaging/mesh_pixel.pyx @@ -44,13 +44,14 @@ from raysect.primitive.mesh.mesh cimport Mesh cimport cython # convenience defines -DEF X = 0 -DEF Y = 1 -DEF Z = 2 +cdef enum: + X = 0 + Y = 1 + Z = 2 -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 + V1 = 0 + V2 = 1 + V3 = 2 cdef class MeshPixel(Observer0D): @@ -167,8 +168,8 @@ cdef class MeshPixel(Observer0D): self._collection_area = 0.0 self._cdf = np.zeros(self._triangles_mv.shape[0]) self._cdf_mv = self._cdf - - # calculate cumulative and total area simultaneously + + # calculate cumulative and total area simultaneously for i in range(self._triangles_mv.shape[0]): # obtain vertex indices @@ -184,7 +185,7 @@ cdef class MeshPixel(Observer0D): ) self._collection_area += triangle_area self._cdf_mv[i] = self._collection_area - + # normalise cumulative area to make cdf self._cdf /= self._collection_area diff --git a/raysect/optical/observer/nonimaging/targetted_pixel.pyx b/raysect/optical/observer/nonimaging/targetted_pixel.pyx index b83b07a0..186a5312 100644 --- a/raysect/optical/observer/nonimaging/targetted_pixel.pyx +++ b/raysect/optical/observer/nonimaging/targetted_pixel.pyx @@ -39,7 +39,7 @@ from raysect.optical.observer.pipeline.spectral import SpectralPowerPipeline0D cimport cython -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) +cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) cdef class TargettedPixel(Observer0D): diff --git a/raysect/optical/ray.pyx b/raysect/optical/ray.pyx index d45e7588..2da22e8f 100644 --- a/raysect/optical/ray.pyx +++ b/raysect/optical/ray.pyx @@ -29,7 +29,7 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from libc.math cimport M_PI as PI, asin, cos +from libc.math cimport M_PI as PI, asin, cos, INFINITY from raysect.core cimport Intersection from raysect.core.math.random cimport probability @@ -39,9 +39,6 @@ from raysect.optical.spectrum cimport new_spectrum from raysect.optical.scenegraph cimport Primitive cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - cdef class Ray(CoreRay): """ diff --git a/raysect/optical/spectrum.pyx b/raysect/optical/spectrum.pyx index 4111ab77..5f1c3c8f 100644 --- a/raysect/optical/spectrum.pyx +++ b/raysect/optical/spectrum.pyx @@ -34,7 +34,7 @@ from raysect.core.math.cython cimport integrate, interpolate from numpy cimport PyArray_SimpleNew, PyArray_FILLWBYTE, NPY_FLOAT64, npy_intp, import_array # Plank's constant * speed of light in a vacuum -DEF CONSTANT_HC = 1.9864456832693028e-25 +cdef const double CONSTANT_HC = 1.9864456832693028e-25 # required by numpy c-api import_array() diff --git a/raysect/primitive/box.pyx b/raysect/primitive/box.pyx index 01d95d09..05720259 100644 --- a/raysect/primitive/box.pyx +++ b/raysect/primitive/box.pyx @@ -30,28 +30,26 @@ # POSSIBILITY OF SUCH DAMAGE. from raysect.core cimport new_point3d, Normal3D, new_normal3d, AffineMatrix3D, Material, new_intersection, BoundingBox3D -from libc.math cimport fabs +from libc.math cimport fabs, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 - -# slab face enumeration -DEF NO_FACE = -1 -DEF LOWER_FACE = 0 -DEF UPPER_FACE = 1 - -# axis enumeration -DEF NO_AXIS = -1 -DEF X_AXIS = 0 -DEF Y_AXIS = 1 -DEF Z_AXIS = 2 +cdef const double EPSILON = 1e-9 + +cdef enum: + # slab face enumeration + NO_FACE = -1 + LOWER_FACE = 0 + UPPER_FACE = 1 + + # axis enumeration + NO_AXIS = -1 + X_AXIS = 0 + Y_AXIS = 1 + Z_AXIS = 2 cdef class Box(Primitive): diff --git a/raysect/primitive/cone.pyx b/raysect/primitive/cone.pyx index d1da1c9f..abe8c513 100644 --- a/raysect/primitive/cone.pyx +++ b/raysect/primitive/cone.pyx @@ -31,22 +31,20 @@ from raysect.core cimport new_point3d, Point3D, new_normal3d, AffineMatrix3D, Material, new_intersection, BoundingBox3D from raysect.core.math.cython cimport solve_quadratic, swap_double, swap_int -from libc.math cimport sqrt +from libc.math cimport sqrt, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 +cdef const double EPSILON = 1e-9 # object type enumeration -DEF NO_TYPE = -1 -DEF CONE = 0 -DEF BASE = 1 +cdef enum: + NO_TYPE = -1 + CONE = 0 + BASE = 1 cdef class Cone(Primitive): diff --git a/raysect/primitive/csg.pyx b/raysect/primitive/csg.pyx index 64ab3e9a..310f7db0 100644 --- a/raysect/primitive/csg.pyx +++ b/raysect/primitive/csg.pyx @@ -31,13 +31,12 @@ # TODO: add more advanced material handling +from libc.math cimport INFINITY + from raysect.core cimport _NodeBase, ChangeSignal, Material, new_ray, new_intersection, Point3D, AffineMatrix3D, BoundingBox3D # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 - -# cython doesn't have a built in infinity definition -DEF INFINITY = 1e999 +cdef const double BOX_PADDING = 1e-9 cdef class CSGPrimitive(Primitive): @@ -112,7 +111,7 @@ cdef class CSGPrimitive(Primitive): def primitive_b(self): """ Component primitive B of the compound CSG primitive. - + :rtype: Primitive """ return self._primitive_b.primitive diff --git a/raysect/primitive/cylinder.pyx b/raysect/primitive/cylinder.pyx index 9030e28d..8bc5804e 100644 --- a/raysect/primitive/cylinder.pyx +++ b/raysect/primitive/cylinder.pyx @@ -31,27 +31,25 @@ from raysect.core cimport AffineMatrix3D, new_normal3d, new_point3d, new_vector3d, Material, new_intersection, BoundingBox3D from raysect.core.math.cython cimport solve_quadratic, swap_double -from libc.math cimport sqrt, fabs +from libc.math cimport sqrt, fabs, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 - -# object type enumeration -DEF NO_TYPE = -1 -DEF CYLINDER = 0 -DEF SLAB = 1 - -# slab face enumeration -DEF NO_FACE = -1 -DEF LOWER_FACE = 0 -DEF UPPER_FACE = 1 +cdef const double EPSILON = 1e-9 + +cdef enum: + # object type enumeration + NO_TYPE = -1 + CYLINDER = 0 + SLAB = 1 + + # slab face enumeration + NO_FACE = -1 + LOWER_FACE = 0 + UPPER_FACE = 1 cdef class Cylinder(Primitive): diff --git a/raysect/primitive/lens/spherical.pyx b/raysect/primitive/lens/spherical.pyx index 31ade37b..96cd5eed 100644 --- a/raysect/primitive/lens/spherical.pyx +++ b/raysect/primitive/lens/spherical.pyx @@ -40,7 +40,7 @@ from libc.math cimport sqrt Basic spherical lens primitives. """ -DEF PADDING = 0.000001 +cdef const double PADDING = 0.000001 cdef class BiConvex(EncapsulatedPrimitive): @@ -128,7 +128,7 @@ cdef class BiConvex(EncapsulatedPrimitive): cdef bint _is_short(self): """ - Do the facing spheres overlap sufficiently to build a lens using just their intersection? + Do the facing spheres overlap sufficiently to build a lens using just their intersection? """ cdef double available_thickness = min( @@ -326,7 +326,7 @@ cdef class PlanoConvex(EncapsulatedPrimitive): # attach to local root (performed in EncapsulatedPrimitive init) super().__init__(lens, parent, transform, material, name) - + cdef void _calc_geometry(self): cdef double radius, radius_sqr @@ -343,7 +343,7 @@ cdef class PlanoConvex(EncapsulatedPrimitive): cdef bint _is_short(self): """ - Does the front sphere have sufficient radius to build the lens with just an intersection? + Does the front sphere have sufficient radius to build the lens with just an intersection? """ cdef double available_thickness = 2 * (self.curvature - self.curve_thickness) @@ -548,7 +548,7 @@ cdef class Meniscus(EncapsulatedPrimitive): cdef bint _is_short(self): """ - Does the front sphere have sufficient radius to build the lens with just an intersection? + Does the front sphere have sufficient radius to build the lens with just an intersection? """ cdef double available_thickness = 2 * self.front_curvature - self.front_thickness diff --git a/raysect/primitive/mesh/mesh.pyx b/raysect/primitive/mesh/mesh.pyx index ac27ddf7..1eec0412 100644 --- a/raysect/primitive/mesh/mesh.pyx +++ b/raysect/primitive/mesh/mesh.pyx @@ -35,7 +35,7 @@ import struct from numpy import array, float32, int32, zeros from raysect.core cimport Primitive, AffineMatrix3D, Normal3D, new_normal3d, Point3D, new_point3d, Vector3D, new_vector3d, Material, Ray, new_ray, Intersection, new_intersection, BoundingBox3D, new_boundingbox3d from raysect.core.math.spatial cimport KDTree3DCore, Item3D -from libc.math cimport fabs +from libc.math cimport fabs, INFINITY from numpy cimport float32_t, int32_t, uint8_t from cpython.bytes cimport PyBytes_AsString cimport cython @@ -45,37 +45,35 @@ The ray-triangle intersection used for the Mesh primitive is an implementation o "Watertight Ray/Triangle Intersection", S.Woop, C.Benthin, I.Wald, Journal of Computer Graphics Techniques (2013), Vol.2, No. 1 """ -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-6 +cdef const double BOX_PADDING = 1e-6 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-6 +cdef const double EPSILON = 1e-6 # convenience defines -DEF X = 0 -DEF Y = 1 -DEF Z = 2 +cdef enum: + X = 0 + Y = 1 + Z = 2 -DEF U = 0 -DEF V = 1 -DEF W = 2 -DEF T = 3 + U = 0 + V = 1 + W = 2 + T = 3 -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 -DEF N1 = 3 -DEF N2 = 4 -DEF N3 = 5 + V1 = 0 + V2 = 1 + V3 = 2 + N1 = 3 + N2 = 4 + N3 = 5 -DEF NO_INTERSECTION = -1 + NO_INTERSECTION = -1 -# raysect mesh format constants -DEF RSM_VERSION_MAJOR = 1 -DEF RSM_VERSION_MINOR = 0 + # raysect mesh format constants + RSM_VERSION_MAJOR = 1 + RSM_VERSION_MINOR = 0 cdef class MeshIntersection(Intersection): @@ -279,9 +277,9 @@ cdef class MeshData(KDTree3DCore): cpdef Point3D vertex(self, int index): """ Returns the specified vertex. - + :param index: The vertex index. - :return: A Point3D object. + :return: A Point3D object. """ if index < 0 or index >= self.vertices_mv.shape[0]: @@ -296,13 +294,13 @@ cdef class MeshData(KDTree3DCore): cpdef ndarray triangle(self, int index): """ Returns the specified triangle. - - The returned data will either be a 3 or 6 element numpy array. The + + The returned data will either be a 3 or 6 element numpy array. The first three element are the triangle's vertex indices. If present, the last three elements are the triangle's vertex normal indices. - + :param index: The triangle index. - :return: A numpy array. + :return: A numpy array. """ if index < 0 or index >= self.vertices_mv.shape[0]: @@ -316,9 +314,9 @@ cdef class MeshData(KDTree3DCore): cpdef Normal3D vertex_normal(self, int index): """ Returns the specified vertex normal. - + :param index: The vertex normal's index. - :return: A Normal3D object. + :return: A Normal3D object. """ if self._vertex_normals is None: @@ -339,9 +337,9 @@ cdef class MeshData(KDTree3DCore): cpdef Normal3D face_normal(self, int index): """ Returns the specified face normal. - + :param index: The face normal's index. - :return: A Normal3D object. + :return: A Normal3D object. """ if index < 0 or index >= self.face_normals_mv.shape[0]: diff --git a/raysect/primitive/parabola.pyx b/raysect/primitive/parabola.pyx index 74f9a6e7..bb85a786 100644 --- a/raysect/primitive/parabola.pyx +++ b/raysect/primitive/parabola.pyx @@ -31,23 +31,21 @@ from raysect.core cimport new_point3d, Point3D, new_normal3d, AffineMatrix3D, Material, new_intersection, BoundingBox3D from raysect.core.math.cython cimport solve_quadratic, swap_double, swap_int -from libc.math cimport sqrt +from libc.math cimport sqrt, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # TODO - Perhaps should be calculated based on primitive scale # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 +cdef const double EPSILON = 1e-9 # object type enumeration -DEF NO_TYPE = -1 -DEF PARABOLA = 0 -DEF BASE = 1 +cdef enum: + NO_TYPE = -1 + PARABOLA = 0 + BASE = 1 cdef class Parabola(Primitive): diff --git a/raysect/primitive/sphere.pyx b/raysect/primitive/sphere.pyx index 081ea3e6..7b43766d 100644 --- a/raysect/primitive/sphere.pyx +++ b/raysect/primitive/sphere.pyx @@ -35,11 +35,11 @@ from raysect.core.math.cython cimport solve_quadratic, swap_double # bounding box and sphere are padded by small amounts to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 -DEF SPHERE_PADDING = 1.000000001 +cdef const double BOX_PADDING = 1e-9 +cdef const double SPHERE_PADDING = 1.000000001 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 +cdef const double EPSILON = 1e-9 cdef class Sphere(Primitive): From 4bf12d8a8aad18b11d0d96e90733aa330df97508 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:28 -0400 Subject: [PATCH 09/11] refactor: remove redundant DEF constants for R_2_PI and R_4_PI --- raysect/core/math/sampler/solidangle.pxd | 3 --- 1 file changed, 3 deletions(-) diff --git a/raysect/core/math/sampler/solidangle.pxd b/raysect/core/math/sampler/solidangle.pxd index 146b6072..0257e312 100644 --- a/raysect/core/math/sampler/solidangle.pxd +++ b/raysect/core/math/sampler/solidangle.pxd @@ -34,9 +34,6 @@ from raysect.core.math cimport Point2D, new_point2d, Point3D, new_point3d, Vecto from raysect.core.math.random cimport uniform from raysect.core.math.cython cimport barycentric_coords, barycentric_interpolation -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) -DEF R_4_PI = 0.07957747154594767 # 1 / (4 * pi) - cdef class SolidAngleSampler: From a87eb2dfe171e70239d1053ba3a76af7c0fa15a0 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:40 -0400 Subject: [PATCH 10/11] refactor: remove unused constants and clean up docstring formatting --- raysect/core/math/transform.pyx | 42 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/raysect/core/math/transform.pyx b/raysect/core/math/transform.pyx index 80e157e5..a7e3f7f0 100644 --- a/raysect/core/math/transform.pyx +++ b/raysect/core/math/transform.pyx @@ -34,13 +34,9 @@ from raysect.core.math.affinematrix cimport new_affinematrix3d from raysect.core.math.point cimport new_point3d cimport cython - cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 -FORWARD = 'forward' -UP = 'up' - cpdef AffineMatrix3D translate(double x, double y, double z): """ @@ -291,16 +287,16 @@ cpdef AffineMatrix3D rotate_basis(Vector3D forward, Vector3D up): cpdef tuple to_cylindrical(Point3D point): """ - Convert the given 3D point in cartesian space to cylindrical coordinates. - + Convert the given 3D point in cartesian space to cylindrical coordinates. + :param Point3D point: The 3D point to be transformed into cylindrical coordinates. :rtype: tuple :return: A tuple of r, z, phi coordinates. - + .. code-block:: pycon - + >>> from raysect.core.math import to_cylindrical, Point3D - + >>> point = Point3D(1, 1, 1) >>> to_cylindrical(point) (1.4142135623730951, 1.0, 45.0) @@ -317,15 +313,15 @@ cpdef tuple to_cylindrical(Point3D point): cpdef Point3D from_cylindrical(double r, double z, double phi): """ Convert a 3D point in cylindrical coordinates to a point in cartesian coordinates. - + :param float r: The radial coordinate. :param float z: The z-axis height coordinate. :param float phi: The azimuthal coordinate in degrees. :rtype: Point3D :return: A Point3D in cartesian space. - + .. code-block:: pycon - + >>> from raysect.core.math import from_cylindrical >>> from_cylindrical(1, 0, 45) @@ -347,21 +343,21 @@ cpdef Point3D from_cylindrical(double r, double z, double phi): cpdef (double, double, double) extract_rotation(AffineMatrix3D m, bint z_up=False): """ Extracts the rotation component of the affine matrix. - + The yaw, pitch and roll can be extracted for two common coordinate conventions by specifying the z_axis orientation: - - forward: +ve z is forward, +ve y is up, +ve x is left + + forward: +ve z is forward, +ve y is up, +ve x is left up: +ve z is up, +ve y is left, +ve x is forward The Raysect default is z axis forward. This function can be switched - to z axis up by setting the z_up parameter to True. - + to z axis up by setting the z_up parameter to True. + The matrix must consist of only rotation and translation operations. - + :param AffineMatrix3D m: An affine matrix. - :param bint z_up: Is the z-axis pointed upwards (default=False). - :return: A tuple containing (yaw, pitch, roll). + :param bint z_up: Is the z-axis pointed upwards (default=False). + :return: A tuple containing (yaw, pitch, roll). """ cdef double yaw, pitch, roll @@ -384,11 +380,11 @@ cpdef (double, double, double) extract_rotation(AffineMatrix3D m, bint z_up=Fals cpdef (double, double, double) extract_translation(AffineMatrix3D m): """ Extracts the translation component of the affine matrix. - + The matrix must consist of only rotation and translation operations. - :param AffineMatrix3D m: An affine matrix. - :return: tuple containing the x, y and z components of the translation. + :param AffineMatrix3D m: An affine matrix. + :return: tuple containing the x, y and z components of the translation. """ return m.get_element(0, 3), m.get_element(1, 3), m.get_element(2, 3) From 1ad5da53fd3dd857dcd7698e2302a4dcec8919e0 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:53 -0400 Subject: [PATCH 11/11] chore: update cython version constraint to 3.1 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a917a861..2e9e4bdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython~=3.0"] +requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython~=3.1"] build-backend = "setuptools.build_meta"