Welcome to Python-flavored Magnum! Please note that, while already being rather stable, this functionality is still considered experimental and some APIs might get changed without preserving full backwards compatibility.

magnum.math module

Math library

Contents

In the C++ API, math types are commonly used via typedefs in the root namespace, only library-level generic code uses things like Math::Vector<size, T>. Since Python doesn’t have templates or generics, there are no generic variants in the magnum.math module, all the concrete types are in the root module with the same names as in the C++ variant.

All math structures are instantiated for the most common sizes and types and so they all share a very similar API. As in C++, main differences are between floating-point types and integral types (one having normalization and projection while the other having bitwise operations) and extra convenience methods added for vectors of particular size.

using namespace Magnum;
Vector4i b{3, 4, 5, 6};
b.b() *= 3;
b.xy() += {1, -1};
Debug{} << b;
// Vector(4, 3, 15, 6)

C++

>>> from magnum import *
>>> b = Vector4i(3, 4, 5, 6)
>>> b.b *= 3
>>> b.xy += (1, -1)
>>> b
Vector(4, 3, 15, 6)

Python

As shown above, all math types are constructible from a (nested) tuple of matching type, matching the convenience of C++11 uniform initializers. As another example, a function accepting a Quaternion will accept a ((x, y, z), w) tuple as well, but not (x, y, z, w), as that is not convertible to a pair of a three-component vector and a scalar.

Magnum math vs Python math

Float vs double overloads

Since Python doesn’t really differentiate between 32bit and 64bit doubles, all scalar functions taking or returning a floating-point type (such as the Deg / Rad types, math.pi or math.sin) use the double variant of the underlying C++ API — the extra arithmetic cost is negligible to the Python-to-C++ function call overhead.

On the other hand, matrix and vector types are exposed in both the float and double variants.

Implicit conversions; NumPy compatibility

All vector classes are implicitly convertible from a tuple of correct size and type as well as from/to type implementing the buffer protocol, and these can be also converted back to lists using list comprehensions. This makes them fully compatible with numpy.ndarray, so the following expressions are completely valid:

>>> Matrix4.translation(np.array([1.5, 0.7, 3.3]))
Matrix(1, 0, 0, 1.5,
       0, 1, 0, 0.7,
       0, 0, 1, 3.3,
       0, 0, 0, 1)
>>> m = Matrix4.scaling((0.5, 0.5, 1.0))
>>> np.array(m.diagonal())
array([0.5, 0.5, 1. , 1. ], dtype=float32)

For matrices it’s a bit more complicated, since Magnum is using column-major layout while numpy defaults to row-major (but can do column-major as well). To ensure proper conversions, the buffer protocol implementation for matrix types handles the layout conversion as well. While the matrix are implicitly convertible from/to types implementing a buffer protocol, they are not implicitly convertible from/to plain tuples like vectors are.

To simplify the implementation, Magnum matrices are convertible only from 32-bit and 64-bit floating-point types ('f' and 'd' numpy dtype). In the other direction, unless overriden using dtype or order, the created numpy array matches Magnum data type and layout:

>>> a = Matrix3(np.array(
...     [[1.0, 2.0, 3.0],
...      [4.0, 5.0, 6.0],
...      [7.0, 8.0, 9.0]]))
>>> a[0] # first column
Vector(1, 4, 7)
>>> b = np.array(Matrix3.rotation(Deg(45.0)))
>>> b.strides[0] # column-major storage
4
>>> b[0] # first column, 32-bit floats
array([ 0.70710677, -0.70710677,  0.        ], dtype=float32)
>>> c = np.array(Matrix3.rotation(Deg(45.0)), order='C', dtype='d')
>>> c.strides[0] # row-major storage (overriden)
24
>>> c[0] # first column, 64-bit floats (overriden)
array([ 0.70710677, -0.70710677,  0.        ])

Major differences to the C++ API

  • All vector and matrix classes implement len(), which is used instead of e.g. Math::Vector::Size. Works on both classes and instances.

  • Math::gather() and Math::scatter() operations are implemented as real swizzles:

    >>> a = Vector4(1.5, 0.3, -1.0, 1.0)
    >>> b = Vector4(7.2, 2.3, 1.1, 0.0)
    >>> a.wxy = b.xwz
    >>> a
    Vector(0, 1.1, -1, 7.2)
  • mat[a][b] = c on matrices doesn’t do the expected thing, use mat[a, b] = c instead

  • Math::BoolVector::set() doesn’t exist, use [] instead

  • While both boolean and bitwise operations on Math::BoolVector behave the same to ensure consistency in generic code, this is not possible to do in Python. Here the boolean operations behave like if any() was applied before doing the operation.

Static constructors and instance method / property overloads

While not common in Python, the Matrix4.scaling() / Matrix4.rotation() methods mimic the C++ equivalent — calling Matrix4.scaling(vec) will return a scaling matrix, while mat.scaling() returns the 3x3 scaling part of the matrix. With Matrix4.translation, it’s a bit more involved — calling Matrix4.translation(vec) will return a translation matrix, while mat.translation is a read-write property accessing the fourth column of the matrix. Similarly for the Matrix3 class.

Functions

def acos(arg0: float, /) -> Rad
Arc cosine
def angle(normalized_a: Vector2, normalized_b: Vector2) -> Rad
Angle between normalized vectors
def angle(normalized_a: Vector3, normalized_b: Vector3) -> Rad
Angle between normalized vectors
def angle(normalized_a: Vector4, normalized_b: Vector4) -> Rad
Angle between normalized vectors
def angle(normalized_a: Vector2d, normalized_b: Vector2d) -> Rad
Angle between normalized vectors
def angle(normalized_a: Vector3d, normalized_b: Vector3d) -> Rad
Angle between normalized vectors
def angle(normalized_a: Vector4d, normalized_b: Vector4d) -> Rad
Angle between normalized vectors
def angle(arg0: Quaternion, arg1: Quaternion, /) -> Rad
Angle between normalized quaternions
def angle(arg0: Quaterniond, arg1: Quaterniond, /) -> Rad
Angle between normalized quaternions
def asin(arg0: float, /) -> Rad
Arc sine
def atan(arg0: float, /) -> Rad
Arc tangent
def cos(arg0: Rad, /) -> float
Cosine
def cross(arg0: Vector2, arg1: Vector2, /) -> float
2D cross product
def cross(arg0: Vector3, arg1: Vector3, /) -> Vector3
Cross product
def cross(arg0: Vector2d, arg1: Vector2d, /) -> float
2D cross product
def cross(arg0: Vector3d, arg1: Vector3d, /) -> Vector3d
Cross product
def dot(arg0: Vector2i, arg1: Vector2i, /) -> int
Dot product of two vectors
def dot(arg0: Vector3i, arg1: Vector3i, /) -> int
Dot product of two vectors
def dot(arg0: Vector4i, arg1: Vector4i, /) -> int
Dot product of two vectors
def dot(arg0: Vector2ui, arg1: Vector2ui, /) -> int
Dot product of two vectors
def dot(arg0: Vector3ui, arg1: Vector3ui, /) -> int
Dot product of two vectors
def dot(arg0: Vector4ui, arg1: Vector4ui, /) -> int
Dot product of two vectors
def dot(arg0: Vector2, arg1: Vector2, /) -> float
Dot product of two vectors
def dot(arg0: Vector3, arg1: Vector3, /) -> float
Dot product of two vectors
def dot(arg0: Vector4, arg1: Vector4, /) -> float
Dot product of two vectors
def dot(arg0: Vector2d, arg1: Vector2d, /) -> float
Dot product of two vectors
def dot(arg0: Vector3d, arg1: Vector3d, /) -> float
Dot product of two vectors
def dot(arg0: Vector4d, arg1: Vector4d, /) -> float
Dot product of two vectors
def dot(arg0: Quaternion, arg1: Quaternion, /) -> float
Dot product between two quaternions
def dot(arg0: Quaterniond, arg1: Quaterniond, /) -> float
Dot product between two quaternions
def intersect(arg0: Range1D, arg1: Range1D, /) -> Range1D
intersect two ranges
def intersect(arg0: Range2D, arg1: Range2D, /) -> Range2D
intersect two ranges
def intersect(arg0: Range3D, arg1: Range3D, /) -> Range3D
intersect two ranges
def intersect(arg0: Range1Di, arg1: Range1Di, /) -> Range1Di
intersect two ranges
def intersect(arg0: Range2Di, arg1: Range2Di, /) -> Range2Di
intersect two ranges
def intersect(arg0: Range3Di, arg1: Range3Di, /) -> Range3Di
intersect two ranges
def intersect(arg0: Range1Dd, arg1: Range1Dd, /) -> Range1Dd
intersect two ranges
def intersect(arg0: Range2Dd, arg1: Range2Dd, /) -> Range2Dd
intersect two ranges
def intersect(arg0: Range3Dd, arg1: Range3Dd, /) -> Range3Dd
intersect two ranges
def intersects(arg0: Range1D, arg1: Range1D, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range2D, arg1: Range2D, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range3D, arg1: Range3D, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range1Di, arg1: Range1Di, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range2Di, arg1: Range2Di, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range3Di, arg1: Range3Di, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range1Dd, arg1: Range1Dd, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range2Dd, arg1: Range2Dd, /) -> bool
Whether two ranges intersect
def intersects(arg0: Range3Dd, arg1: Range3Dd, /) -> bool
Whether two ranges intersect
def join(arg0: Range1D, arg1: Range1D, /) -> Range1D
Join two ranges
def join(arg0: Range2D, arg1: Range2D, /) -> Range2D
Join two ranges
def join(arg0: Range3D, arg1: Range3D, /) -> Range3D
Join two ranges
def join(arg0: Range1Di, arg1: Range1Di, /) -> Range1Di
Join two ranges
def join(arg0: Range2Di, arg1: Range2Di, /) -> Range2Di
Join two ranges
def join(arg0: Range3Di, arg1: Range3Di, /) -> Range3Di
Join two ranges
def join(arg0: Range1Dd, arg1: Range1Dd, /) -> Range1Dd
Join two ranges
def join(arg0: Range2Dd, arg1: Range2Dd, /) -> Range2Dd
Join two ranges
def join(arg0: Range3Dd, arg1: Range3Dd, /) -> Range3Dd
Join two ranges
def lerp(normalized_a: Quaternion, normalized_b: Quaternion, t: float) -> Quaternion
Linear interpolation of two quaternions
def lerp(normalized_a: Quaterniond, normalized_b: Quaterniond, t: float) -> Quaterniond
Linear interpolation of two quaternions
def lerp_shortest_path(normalized_a: Quaternion, normalized_b: Quaternion, t: float) -> Quaternion
Linear shortest-path interpolation of two quaternions
def lerp_shortest_path(normalized_a: Quaterniond, normalized_b: Quaterniond, t: float) -> Quaterniond
Linear shortest-path interpolation of two quaternions
def sin(arg0: Rad, /) -> float
Sine
def sincos(arg0: Rad, /) -> typing.Tuple[float, float]
Sine and cosine
def slerp(normalized_a: Quaternion, normalized_b: Quaternion, t: float) -> Quaternion
Spherical linear interpolation of two quaternions
def slerp(normalized_a: Quaterniond, normalized_b: Quaterniond, t: float) -> Quaterniond
Spherical linear interpolation of two quaternions
def slerp_shortest_path(normalized_a: Quaternion, normalized_b: Quaternion, t: float) -> Quaternion
Spherical linear shortest-path interpolation of two quaternions
def slerp_shortest_path(normalized_a: Quaterniond, normalized_b: Quaterniond, t: float) -> Quaterniond
Spherical linear shortest-path interpolation of two quaternions
def tan(arg0: Rad, /) -> float
Tangent

Data

e = 2.718281828459045
Euler’s number
inf = inf
Positive \infty
nan = nan
Quiet NaN
pi = 3.141592653589793
\pi
pi_half = 1.5707963267948966
Half of a \pi
pi_quarter = 0.7853981633974483
Quarter of a \pi
sqrt2 = 1.414213562373095
Square root of 2
sqrt3 = 1.7320508075688772
Square root of 3
sqrt_half = 0.7071067811865475
Square root of \frac{1}{2}
tau = 6.283185307179586
\tau , or 2 \pi