#kinematics #unit-system #math #operation

syunit

A small library that contains some basic units to help structuring kinematics and robotic programming in rust

5 unstable releases

0.4.0 Oct 23, 2024
0.3.2 Oct 17, 2024
0.2.2 Oct 9, 2024
0.2.1 May 25, 2024
0.1.1 Feb 24, 2024

#30 in Robotics


Used in 3 crates

Custom license

55KB
785 lines


A small library that contains some basic units to help structuring kinematics and robotic programming in rust. The library uses rusts tuple structs to create a compile-time checking of correct unit, variable and function usage.

Quick introduction

In many functions for kinematics and robotics, it sometimes becomes unclear which type of unit is desired to be used.

/// Relative movement
fn move_distance(dist : f32, vel : f32) {
    // ...
}

/// Absolute movement
fn move_to_position(pos : f32, vel : f32) {
    // ...
}

Even if most code is not as horribly documented, nothing stops a developer from accidently plugging in an absolute distance into a function that takes a relative one. Here comes this library into play:

use syunit::prelude::*;

/// Relative movement
fn move_distance(dist : Radians, vel : RadPerSecond) {
    // ...
}

/// Absolute movement
fn move_to_position(pos : PositionRad, vel : RadPerSecond) {
    // ...
}

Each unit is represented by a f32 enclosed into a tuple struct, making them simple but also their own type!

Why these units are helpful not only for documentation is explained in the flowing chapters:

Explicit syntax

As rust always prefers explicit syntax, so does this library. The unit types cannot be converted back to a f32 without calling into().

use syunit::prelude::*;

fn requires_f32(value : f32) {
    // ...
}

fn requires_velocity(value : MMPerSecond) {
    // ...
}

// Every unit is created by the tuple struct constructor using a `f32` value
let abs_pos = PositionMM(10.0);

requires_f32(abs_pos);
// error[E0308]: mismatched types
// |
// | requires_f32(abs_pos) // ERROR! => Type `PositionMM` cannot be used as `f32`
// | ------------ ^^^^^ expected `f32`, found `PositionMM`
// | |
// | arguments to this function are incorrect
// |

requires_velocity(abs_pos);
// error[E0308]: mismatched types
// |
// | requires_f32(abs_pos);
// | ------------ ^^^^^ expected `MMPerSecond`, found `PositionMM`
// | |
// | arguments to this function are incorrect
// |

Operations and automatic type evaluation

The library comes with a lot of implementations in addition to the units, making it possible to do a lot of operations with these units and letting the compiler automatically evaluate the resulting units for you

use syunit::prelude::*;

// Radial / Linear
assert_eq!(RadPerSecond(4.0) * Millimeters(2.0), MMPerSecond(8.0));
assert_eq!(RadPerSecond2(-3.0) * Millimeters(2.0), MMPerSecond2(-6.0));

// Seconds / Hertz
assert_eq!(Hertz(4.0), 1.0 / Seconds(0.25));
assert_eq!(1.0 / Hertz(5.0), Seconds(0.2));

// Time to build up speed
assert_eq!(MMPerSecond(6.0) / MMPerSecond2(2.0), Seconds(3.0));

// Forces
assert_eq!(NewtonMeters(-5.0) / KgMeter2(2.0), RadPerSecond2(-2.5));
assert!((Newtons(3.0) / Kilogramms(1.5) - MMPerSecond2(2000.0)).abs().0 < 0.001);  // Automatic conversion

// ...

Another helpful unit type are Positions, they help differentiating between absolute and relative distances.

use syunit::prelude::*;

// Difference between two positions is a relative distance
assert_eq!(PositionMM(5.0) - PositionMM(3.0), Millimeters(2.0));

// Radial position math
assert_eq!(PositionRad(3.0) + Radians(2.0), PositionRad(5.0));
assert_eq!(PositionRad(3.0) - PositionRad(2.0), Radians(1.0)); 

A very special unit is Seconds, dividing or multipling by it often changes units.

use syunit::prelude::*;

// Travelling a distance of 6mm in 2 seconds gives a velocity of 3mm/s
assert_eq!(Millimeters(6.0) / Seconds(2.0), MMPerSecond(3.0));
// Accelerating to a velocity of 3mm/s in 2 seconds gives an acceleration of 1.5mm/s^2
assert_eq!(MMPerSecond(3.0) / Seconds(2.0), MMPerSecond2(1.5));
// Travelling with 3mm/s for 3 seconds gives a total distance of 9mm
assert_eq!(MMPerSecond(3.0) * Seconds(3.0), Millimeters(9.0));

Unitsets

There is also a tool for defining functions in a more general way, with the help of UnitSets!

use syunit::prelude::*;

fn get_distance<U : UnitSet>(vel : U::Velocity, time : U::Time) -> U::Distance {
    vel * time      // Compiler automatically checks if the types match
}

fn time_for_dist_accelerating<U : UnitSet>(distance : U::Distance, vel_start : U::Velocity, vel_end : U::Velocity) -> U::Time {
    let vel_avg = (vel_start + vel_end) / 2.0;
    distance / vel_avg
}

assert_eq!(get_distance::<MetricMM>(MMPerSecond(4.0), Seconds(2.0)), Millimeters(8.0));     // Using linear metric mm
assert_eq!(time_for_dist_accelerating::<Rotary>(Radians(3.0), RadPerSecond(2.0), RadPerSecond(4.0)), Seconds(1.0));     // Using rotary units

Metric and Imperial

The library also includes imperial units and conversions between them.

use syunit::prelude::*;
use syunit::imperial::*;

let millimeters = Millimeters(25.4);

assert_eq!(Meters(1.0), Millimeters(1000.0).into());
assert_eq!(Inches(1.0), Millimeters(25.4).into());

serde implementation

All the units implement serde::Serialize and serde::Deserialize if the "serde" feature is enabled, which is the case by default.

Issues and improvements

Please feel free to create issues on the github repo!

Dependencies

~0.3–1MB
~21K SLoC