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.0.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.0-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.0-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.0-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.0.tar.gz.

File metadata

  • Download URL: linalg3d-0.3.0.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.0.tar.gz
Algorithm Hash digest
SHA256 d2f598100285babe995194e9b05ca2c0e83ec247f0e8aae253b274137bf04403
MD5 78f61e77c3bff58bb8dcc56f37adec89
BLAKE2b-256 0a096dea38f024d5fb8f0d6a88a1a87b9cc6faa055d9b6f22be919e0a86cd41a

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.0.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.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for linalg3d-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b5c8e98020722dfff8dc82a2d0262196bacbe61cef6d5892f9cd0a26ea88efc8
MD5 4f9f81d8e82b8b4702d5b3fc803dcbe7
BLAKE2b-256 324b6d2b13a7fcf42da008b7781f7f5ca32be64cc1a0c88c274f34f210e336ce

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.0-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.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for linalg3d-0.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 6cacd2fa4b10d46e665b352c6ee5a7d549b677e2fa342e653f7a08ba3bb3a555
MD5 48c835e475c4a7bf4833ad1b8648f074
BLAKE2b-256 257b164c0679e7bf59bb092031a247f054295a3ae9948e404e389a7c6d620715

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.0-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.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for linalg3d-0.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 1f779f3c8b3cf240830b9f564bdf0c04cf235de680f4b757ecadc5eaf21f7d11
MD5 f48e57e36e4005f2818a2f88d2f9d242
BLAKE2b-256 a12c07ef9014de61e6f57fe7527f6f92fcfe029ded00a9cc4a2d8fccdbb29476

See more details on using hashes here.

Provenance

The following attestation bundles were made for linalg3d-0.3.0-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