Skip to main content

Fast constexpr linear algebra for C++23 with zero-copy NumPy/SciPy interop

Project description

linalg3d

Header-only constexpr linear algebra library for C++23. Provides type-safe 2D, 3D, and 4D math primitives that evaluate entirely at compile time.

Features

  • Fully constexpr — all operations evaluate at compile time via gcem, with if consteval dispatch to std:: math and SIMD at runtime
  • SIMD optimized — SSE2/AVX/FMA (x86) and NEON (ARM) intrinsics for matrix multiply/inverse via automatic if consteval dispatch
  • Python bindingsnanobind-based package with zero-copy NumPy and SciPy interop
  • Type-safe anglesAngle<RADIANS> vs Angle<DEGREES> prevents unit-mismatch bugs at the type level
  • 2D, 3D, 4DVector2/Matrix2, Vector3/Matrix3, Vector4/Matrix4
  • Quaternions — multiplication, rotation, SLERP interpolation, Euler angle conversion
  • std::expected — matrix inverse() returns std::expected<Matrix, MatrixError> instead of silent failure
  • fmt support — optional format.hpp header with fmt::formatter specializations for all types
  • Zero dependencies at runtime — gcem is header-only (compile-time only), fmt is optional

Usage

#include <linalg3d/linalg.hpp>

using namespace linalg3d;

// Compile-time vector math
constexpr Vector3 a{1.0, 2.0, 3.0};
constexpr Vector3 b{4.0, 5.0, 6.0};
constexpr auto c = a.cross(b);
static_assert(c == Vector3{-3.0, 6.0, -3.0});

// Type-safe angles
constexpr Angle<AngleType::DEGREES> heading{90.0};
constexpr auto rad = heading.to_radians();

// Quaternion rotation
constexpr auto q = euler_angles_to_quaternion(EulerAngles{0.0, 0.0, PI / 2.0});
constexpr Vector3 rotated = q * Vector3{1.0, 0.0, 0.0};

// Safe matrix inversion
constexpr Matrix3 m{2.0, -1.0, 0.0, -1.0, 2.0, -1.0, 0.0, -1.0, 2.0};
constexpr auto inv = m.inverse();
static_assert(inv.has_value());

// SLERP interpolation
constexpr auto mid = slerp(Quaternion::identity(), q, 0.5);

// Angle between two vectors (radians)
constexpr auto theta = angle_between(Vector3{1.0, 0.0, 0.0}, Vector3{0.0, 1.0, 0.0});
static_assert(theta > 1.57 && theta < 1.58); // pi/2

// Angle between two quaternion orientations (shortest arc, radians)
auto rot_angle = angle_between(Quaternion::identity(), q);

// 2D and 4D
constexpr Vector2 v2{3.0, 4.0};
static_assert(v2.norm_sq() == 25.0);
constexpr Vector4 v4{1.0, 2.0, 3.0, 4.0};
constexpr auto identity4 = Matrix4::identity();

For fmt::print support:

#include <linalg3d/format.hpp>
fmt::print("{}\n", Vector3{1.0, 2.0, 3.0}); // (1, 2, 3)
fmt::print("{}\n", Quaternion::identity());   // Quaternion(w=1, x=0, y=0, z=0)

Types

Type Header Description
Vector2 vector2.hpp 2D vector with dot/cross(scalar)/norm
Vector3 vector3.hpp 3D vector with dot/cross/norm
Vector4 vector4.hpp 4D vector with dot/norm
Matrix2 matrix2x2.hpp 2x2 matrix with inverse/determinant
Matrix3 matrix3x3.hpp 3x3 matrix with inverse/determinant
Matrix4 matrix4x4.hpp 4x4 matrix with inverse/determinant
Quaternion quaternion.hpp Unit quaternion with rotation/SLERP/angle
Angle<T> angle.hpp Type-safe angle (radians/degrees)
EulerAngles<T> euler_angles.hpp Pitch/yaw/roll triplet

Benchmarks

Measured with nanobench (GCC 14, -O3 -march=native, Ubuntu 24.04). Compared against Eigen 3.4 on equivalent operations:

Operation linalg3d (ns) Eigen (ns) Ratio
Vector3 dot 0.76 0.54 1.4x
Vector3 cross 0.80 0.96 0.8x
Vector3 norm 1.49 1.49 1.0x
Vector3 normalized 3.50 3.48 1.0x
Matrix3 multiply 3.60 2.64 1.4x
Matrix3 inverse 7.11 7.23 0.98x
Matrix4 inverse 14.78 10.72 1.4x
Matrix4 multiply 2.90 2.50 1.2x
Quaternion multiply 1.75 1.21 1.4x
Quaternion inverse 2.02 8.77 0.23x
Quaternion*Vector3 2.01 2.42 0.8x
slerp 21.16 22.12 0.96x
quaternion_to_euler 18.80 26.67 0.7x
Angle::sin 4.22
Angle::cos 3.23

Ratio = linalg3d / Eigen (lower is better for linalg3d; bold = linalg3d wins).

Performance summary vs Eigen

  • linalg3d faster: Matrix3 inverse (0.98x), quaternion inverse (4.3x faster), cross product (0.8x), quaternion*vector (0.8x), slerp (0.96x), quaternion-to-euler (0.7x)
  • On par (0.9-1.2x): vector norm, normalized, add, matrix transpose, matrix4 multiply
  • Eigen faster: matrix3 multiply (1.4x), matrix4 inverse (1.4x), quaternion multiply (1.4x), dot product (1.4x)
  • Unique to linalg3d: all operations are constexpr (Eigen has none), type-safe angles, std::expected error handling, SIMD dispatched via if consteval
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target linalg3d_bench
./build/linalg3d_bench

Examples

IMU attitude interpolation

Smoothly interpolate between two IMU quaternion readings for sub-frame timing.

#include <linalg3d/linalg.hpp>
using namespace linalg3d;

// Two IMU samples at different timestamps
const Quaternion q_t0{0.998, 0.01, 0.02, 0.05};
const Quaternion q_t1{0.997, 0.01, 0.03, 0.06};

// Interpolate at 60% between the two samples
auto q_mid = slerp(q_t0, q_t1, 0.6);

// How far apart are these orientations?
double delta = angle_between(q_t0, q_t1); // radians

Rotating a body-frame vector to NED

Convert a sensor reading from body frame to North-East-Down using an attitude quaternion.

// Sensor reports acceleration in body frame
constexpr Vector3 accel_body{0.1, -0.05, -9.81};

// Current attitude from IMU (quaternion: w, x, y, z)
const Quaternion attitude{0.998, 0.01, 0.02, 0.05};

// Rotate to NED frame
Vector3 accel_ned = attitude * accel_body;

Compile-time coordinate transform validation

Verify a rotation matrix at compile time -- errors caught before the code even runs.

constexpr auto q = euler_angles_to_quaternion(EulerAngles{0.0, 0.0, PI / 2.0});
constexpr auto mat = quaternion_to_matrix(q);
constexpr auto v = mat * Vector3{1.0, 0.0, 0.0};
// 90 deg yaw: x-axis maps to y-axis
static_assert(fabs(v.x) < 1e-10);
static_assert(fabs(v.y - 1.0) < 1e-10);

Python

Installation

pip install linalg3d

Or build from source:

pip install "./python[test]"

Python usage

from linalg3d import Vector3, Matrix3, Quaternion, slerp
import numpy as np

# Vectors
v = Vector3(1.0, 2.0, 3.0)
print(v.norm(), v.normalized())

# Zero-copy NumPy interop — no data copying
arr = np.asarray(v.numpy())     # shares memory with v
arr[0] = 99.0
assert v.x == 99.0              # modification reflected

# Matrices
m = Matrix3.identity()
arr = np.asarray(m.numpy())     # zero-copy 3x3 view
det = m.determinant()
inv = m.inverse()               # returns None if singular

# Quaternion rotation
import math
q = Quaternion(math.cos(math.pi/4), 0, 0, math.sin(math.pi/4))
rotated = q.rotate(Vector3(1, 0, 0))

# SLERP
mid = slerp(Quaternion(), q, 0.5)

# SciPy interop (convention: [x, y, z, w])
from scipy.spatial.transform import Rotation
scipy_arr = np.asarray(q.to_scipy())   # [x,y,z,w]
rot = Rotation.from_quat(scipy_arr)
q2 = Quaternion.from_scipy(scipy_arr)  # back to linalg3d

Build (C++)

Requires CMake 3.25+ and a C++23 compiler. Dependencies (gcem, fmt, doctest, nanobench) are fetched automatically via FetchContent if not found on the system.

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
ctest --test-dir build

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

linalg3d-0.3.1.tar.gz (11.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

linalg3d-0.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (92.0 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

linalg3d-0.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (92.0 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

linalg3d-0.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (93.1 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

File details

Details for the file linalg3d-0.3.1.tar.gz.

File metadata

  • Download URL: linalg3d-0.3.1.tar.gz
  • Upload date:
  • Size: 11.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for linalg3d-0.3.1.tar.gz
Algorithm Hash digest
SHA256 902fefe234952b9bfbbc05b269bd35091e1d32f937158329c114ef1e236bdc7e
MD5 2ae76b15b0a482cba0bdaf0fd13f9cb6
BLAKE2b-256 4b3c8230b4c4de2da64e95b114312f3ce4ea2998d72432b21828dd16498ad78d

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.1.tar.gz:

Publisher: publish.yml on PavelGuzenfeld/linalg3d

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file linalg3d-0.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for linalg3d-0.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 28fc6bc3c13db87bcf6b049a66d30a095f38cab789ddcbcb6e0fde1ebb417c26
MD5 8fce929a560a6653946c4a630855e5f9
BLAKE2b-256 010911d4d543bbd25c2ef6726216e1dd94abf27ba5a29f75acaa7a6949e60704

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on PavelGuzenfeld/linalg3d

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file linalg3d-0.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for linalg3d-0.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 ba9a45e0f4f2d6d8f079bbf26ed36b73584979950eb23ca99cfbf91d39d4aa52
MD5 754c3fdb47e6d039c85dca62628d1331
BLAKE2b-256 4b4e7f807185181c019ba2e1f2ed92bb46723c35cb656ee50627c88c2d4cd1bc

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on PavelGuzenfeld/linalg3d

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file linalg3d-0.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for linalg3d-0.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 1a1d7f3a21bc8c8ba81fb29abfa5cb985a8902379e64a3ac8868218de4479b4d
MD5 b0b902ff8d79ad1d205231aef26aeeb4
BLAKE2b-256 7dc432d7e2795114dc936ff9872479958e7879411f985e45cf6c81c3ef6c0469

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl:

Publisher: publish.yml on PavelGuzenfeld/linalg3d

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page