Library / SDK
fu5ha/ultraviolet avatar
fu5ha/ultraviolet

ultraviolet: a Rust math crate built around wide types and geometric algebra rotors

A wide linear algebra crate for games and graphics.

799 stars93 forksRustLicense varies

At a glance

What is it?
ultraviolet is a Rust crate for the linear and geometric algebra that games and graphics need, with a deliberate refusal of generics in the API and a parallel set of wide types that hold four or eight scalars in SIMD lanes. Rotations use Rotors from geometric algebra rather than quaternions, and the crate is built on the wide crate for the SIMD primitives.
Who is it for?
ultraviolet fits a Rust graphics or game developer who wants a maths library whose wide types are a first class citizen rather than an afterthought, and who is willing to express rotations as Rotors instead of quaternions. It does not fit a team that wants a general purpose nalgebra replacement with a familiar API, or a project that cannot take a dependency on a crate with an unusual design.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 19 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

No generics in the public API, on purpose

The crate describes itself as being for computer graphics and games related linear and geometric algebra, but fast, both in terms of productivity and in terms of runtime performance. Those are two separate claims and they are worth separating.

The productivity claim is that ultraviolet uses no generics and is designed to be as straightforward of an interface as possible, resulting in fast compilation times and clear code. The consequence of no generics and no Rust type system tricks is that errors are clear and concise and easy to parse and fix for the user.

That is a genuinely unusual position in Rust. Most maths crates are generic over the scalar type, which is what lets one implementation serve f32 and f64 and be generic over a user-defined type. Ultraviolet takes the opposite route and instead ships a separate concrete type per precision, with the wide variants as further types. The trade is that you write the type out rather than parameterise it, and the payoff is compile times and readable error messages.

The crate is published on crates.io as ultraviolet with documentation on docs.rs, and the version in the repository manifest is 0.11.0 on edition 2024, with the description a crate to do linear algebra, fast. The author listed is Gray Olson, and the repository field in the manifest points at github.com/termhn/ultraviolet.

The repository is small in file count: a src directory, a docs directory, a CHANGELOG.md, a release.toml and a derivations directory, and the keywords are simd, wide, graphics, math and linear-algebra.

The wide types and the AoSoA layout

The performance side of the design is that every type comes in two kinds with nearly identical functionality: one with usual scalar f32 values, and the other a wide type which uses SIMD f32x4 vectors for each value. The README says this design is clear and explicit in intent and that it also allows code to take full advantage of SIMD.

The wide types use an SoA, Structure of Arrays, architecture such that each wide data structure contains the data for 4 or 8 of its associated data type and will do any operation on all the SIMD lanes at the same time. A Vec3x8 is equivalent to 8 Vec3s bundled together into one data structure.

The claimed payoff is potentially much faster than a standard AoS, Array of Structs, layout, with the README giving a factor of 10, and it does depend on your workload and algorithm requirements. The caveat is important and stated in the same breath: algorithms must be carefully architected to take full advantage of this, and doing so can be easier said than done, especially if your algorithm involves significant branching.

The credit claim is specific. Ultraviolet was the first Rust math library to be designed in this AoSoA manner, though nalgebra now supports it for several of its data structures as well. That is a fair statement of priority, and the mention of nalgebra matters for the choice you are about to make.

The SIMD primitives come from a separate crate, the wide crate at version 1.7.0, which is a non-optional dependency in the manifest even though a commented-out line shows the author considered vendoring it as an optional path.

Rotors instead of quaternions, and the derivations folder

The most distinctive decision in the library is that rotations are represented with Rotors, a concept taken from geometric algebra, for both 2d and 3d, instead of complex number algebra and quaternion algebra.

The argument is made in two parts. First, that the derivation of the maths is simple to understand, with all the derivations for the code implemented in the Rotor structs written out in the derivations folder of the repository, derived manually as part of the implementation. Second, that quaternions are often seen as black boxes that we use to do rotations because they have some nice properties, but that we do not really understand, whereas a Rotor can be used the same way and also understood.

The third argument is that in some sense Rotors are more correct than quaternions, because they facilitate a more proper understanding of rotation as something that occurs within a plane rather than around an axis, as it is generally thought. And they generalise to 4 and even higher dimensions, where a Rotor4 would retain all the properties of a Rotor3 but do rotation in four dimensions, which is simply not possible with quaternions.

For the programmer the practical effect is that you use the Rotor3 type in place of a quaternion, and the README says you can expect it to do basically all the same things a quaternion does. In fact quaternions are directly isomorphic to rotors, meaning they are essentially the same thing formulated differently.

The request to contributors is characteristically direct: if something is missing, open an issue on the tracker or find the author on the Rust community discord server, and he will try to add it if he believes it fits with the vision of the library.

Feature flags, defaults and the interop story

To improve build times further, ultraviolet puts various functionality under feature flags. The README gives the example that the 2d and 3d projective geometric algebras as well as f64 and integer types are disabled by default, and that you enable them with the corresponding crate feature flags in your Cargo.toml.

The list of available features is short. f64 enables f64 bit wide floating point support, with the naming convention D[Type] so DVec3x4 would be a collection of 4 three dimensional vectors with f64 precision each. int enables integer vector types. bytemuck enables casting of many types to byte arrays, for use with graphics APIs. mint enables interoperation with other maths crates through the mint interface. num-traits enables identity traits for interoperation with other maths crates. serde enables Serialize and Deserialize implementations for many scalar types, and encase enables encase trait implementations for the ultraviolet types.

There is a discrepancy worth knowing about. The README says the f64 and integer types are disabled by default, while the manifest in the repository declares default as the int and f64 features both enabled. The published crate on crates.io is the one whose defaults apply to a user, so check the features list on the docs.rs page for the version you pull.

The interop features are what make the crate usable inside a larger graphics stack. mint is the common interface that several maths crates agree on, num-traits provides the identity traits so that ultraviolet types can sit next to other maths types in generic code, bytemuck gives the byte casts that graphics APIs need, and encase covers the shader encoding path.

The crate is currently dogfooded in the author's ray tracer, rayn, and is used by various independent Rust game developers, and the README's own summary of its scope is that it does what those users have currently needed it to do.

Porting a scalar loop to wide types

The examples section opens with Euler integration, and the framing is deliberately reassuring: if the details of the method do not matter because you are not implementing physics simulation, the method is still common in games.

The scalar version of the function is:

rust
fn integrate(
    pos: &mut [uv::Vec3],
    vel: &mut [uv::Vec3],
    acc: &[uv::Vec3],
    dt: f32,
) {
    for ((position, velocity), acceleration) in pos.iter_mut().zip(vel).zip(acc) {
        *velocity = *velocity + *acceleration * dt;
        *position = *position + *velocity * dt;
    }
}

The code loops over each set of corresponding position, velocity and acceleration vectors, adjusts the velocity by the acceleration scaled by the elapsed time, then adjusts the position by the velocity scaled by the elapsed time.

The point of the example is the last step, and the README is explicit about the conditions. These are all multiplication, addition and assignment operators that need to be applied in the same way to all of the variables in question, and if you are doing the same basic maths operations on multiple floating point values with no conditionals, no ifs, then porting to wide data types and parallel processing is quite simple. The ported function changes the data types and that is done.

That caveat is the whole difficulty of SIMD in practice. A physics loop with no branches vectorises for free, and the same loop with a collision test in it does not. The library gives you the wide types and the arithmetic, and the architecture work of writing your algorithm so it can use them is left to you.

Where ultraviolet is the wrong choice

The first limitation is the migration cost from a conventional maths crate. If your code already uses nalgebra, ultraviolet is a different API with different type names, and the wide types are not a drop-in replacement for the scalar ones you have. Switching is a rewrite of the maths layer rather than a version bump.

The second is the geometric algebra commitment. Rotors are isomorphic to quaternions and do the same things, and the author argues they are better understood, but understanding them properly means reading the derivations, and if your team does not want to learn geometric algebra then a Rotor3 is a less familiar name for a familiar thing. There is also a smaller support ecosystem: quaternion types are ubiquitous, and Rotor3 is not.

The third is the branch problem, and the README raises it itself. A factor of 10 is available for workloads that vectorise, and significant branching is exactly what stops a loop from vectorising. A game with a lot of conditional logic in its hot loop will not get the win, and a codebase that has to be architected around wide types is a codebase that is harder to read. The productivity claim and the performance claim pull against each other here.

The comparison to nalgebra is the fair one, and the README is honest about it, since nalgebra now supports the AoSoA approach for several of its data structures. If you already use nalgebra, the case for switching is the wide type design and the geometric algebra rather than the SIMD layout alone. If you are choosing from scratch, the no-generics and compile-time argument is the strongest one, and the derivation documents are the deciding factor for a team that will read them.

Editorial conclusion

ultraviolet fits a Rust graphics or game developer who wants a maths library whose wide types are a first class citizen rather than an afterthought, and who is willing to express rotations as Rotors instead of quaternions. It does not fit a team that wants a general purpose nalgebra replacement with a familiar API, or a project that cannot take a dependency on a crate with an unusual design. Before adopting it, check the feature flags you need since f64 and the integer types are off by default in the published build while the repository manifest enables them, and read the derivations folder, since the author states the maths behind the Rotor types is written out there and it is the only real explanation of the design.

Frequently asked questions

What is ultraviolet and what is unusual about its design?

It is a Rust crate for the linear and geometric algebra that games and graphics need, published on crates.io as ultraviolet. It uses no generics in the public API, which the author credits for fast compilation times and clear error messages, and it ships every type in both a scalar f32 form and a wide SIMD form.

How do the wide types in ultraviolet work?

They use an SoA, Structure of Arrays, architecture where each wide structure holds the data for 4 or 8 of its associated type and operates on all the SIMD lanes at the same time, so a Vec3x8 is equivalent to 8 Vec3s bundled together. The README says this can be a factor of 10 faster than an Array of Structs layout, depending on the workload.

Why does ultraviolet use Rotors instead of Quaternions?

Rotors come from geometric algebra and the author argues their derivation is simple to understand and written out in the derivations folder of the repository, that quaternions are often used as black boxes, and that Rotors generalise to four dimensions where quaternions cannot. Quaternions are directly isomorphic to Rotors and the Rotor3 type does essentially the same things.

Which Cargo features does ultraviolet expose?

f64 for double precision types using the D prefix naming, int for integer vector types, bytemuck for casting types to byte arrays for graphics APIs, mint for interoperation with other maths crates, num-traits for identity traits, serde for Serialize and Deserialize on scalar types, and encase for the encase traits. The README notes the 2d and 3d projective geometric algebras and the f64 and integer types are disabled by default in the published build.

How do I port a scalar loop in ultraviolet to wide types?

The example given is Euler integration: where the loop applies only multiplication, addition and assignment to position, velocity and acceleration with no conditionals, changing the data types to the wide versions is the whole port. The README is explicit that significant branching is what prevents a loop from taking advantage of the wide layout.

Official sources

  1. fu5ha/ultraviolet on GitHub
  2. Issues
  3. Project website
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/fu5ha-ultraviolet.svg)](https://hysenlabs.com/projects/fu5ha-ultraviolet)

Was this useful?