Skip to content

bgreni/Kelvin

Repository files navigation

Kelvin

License ci_badge CodeQL

A powerful dimensional analysis library written in Mojo for all your scientific computing needs. Heavily inspired by uom.

Defining a Quantity

To define a specific unit, you simply need to create an comptime to Quantity with the particular dimensions defined. Each dimension also requires a Ratio and a suffix string. The ratio defines the relationship between it and the unitary amount. All dimensions that do not exist for a given quantity are given Dimension.Invalid.

# Base unit for time, `Second`
comptime Meter = Quantity[
    Dimensions[
        Dimension[1, Ratio.Unitary, "m"](), # Length
        Dimension.Invalid, # Mass
        Dimension.Invalid, # Time
        Dimension.Invalid, # Electric current
        Dimension.Invalid, # Temperature
        Dimension.Invalid, # Substance Amount
        Dimension.Invalid, # Luminosity
        Angle.Invalid, # Angle
    ](),
    ...,
]

# Use Ratio.Kilo to create a `Kilometer`
comptime Kilometer = Quantity[
    Dimensions[
        Dimension[1, Ratio.Kilo, "km"](),
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Angle.Invalid,
    ](),
    ...,
]

# Quantity with a weird conversion ratio
comptime Mile = Quantity[
    Dimensions[
        Dimension[1, Ratio[1609344, 1000]().simplify(), "mile"](),
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Dimension.Invalid,
        Angle.Invalid,
    ](),
    ...,
]

With these simple units defined, we can use arithmetic syntax on the Dimensions struct to create derivative units very easily. Here we can define Velocity in a single line of code.

comptime MetersPerSecond = Quantity[Meter.D / Second.D, ...,]
comptime MetersSquared = Quantity[Meter.D**2, ...,]

comptime kg = Kilogram.D
comptime m = Meter.D
comptime s = Second.D
comptime Newton = Quantity[kg * m * (s ** -2)]

Quantity uses the mojo builtin SIMD type, so users can also supply a vector width parameter after the DType.

comptime SIMDSeconds = Second[_, 4]
comptime SIMDMeter = Meter[_, 4]
print(SIMDMeter(40) / SIMDSeconds(10)) # [4.0, 4.0, 4.0, 4.0] m^1 s^-1

Numerical Type

Quantity accepts a DType parameter to define the numerical type and bitwidth of the underlying value. The default is DType.float64.

var s = Second(10.0) # Will be a float64

comptime IntSecond = Second[DType.int64]
var is = IntSecond(10) # will be int64

Quantity Arithmetic

Since the dimensions of a quantity are a dynamic part of the type system, we can also do type-safe quantity multiplication and division. Though they must have matching scale on all valid dimensions

var velocity: MetersPerSecond = Meter(10) / Second(2)
var area: MetersSquared = Meter(10) * Meter(20)

var a = Meter(10) * Mile(20) # Nope

# Power must be an IntLiteral so it's compile time known
area = Meter(10) ** 2

Addition and subtraction are only defined on matching quantities.

var a = Meter(10) + Meter(10)
var b = Meter(10) + Mile(20) # Nope
var c = Meter(10) + Second(10) # Nope

You can also use scalar values for multiplication and division.

var m = Meter(10)
m = m * 5
m /= 5

Quantity Conversions

Quantities with matching dimensions, but different scale can be casted to one another.

var m = Minute(cast_from=Second(600))
var s = Second(cast_from=m)

Sharp Edges

Dimensions operations are unchecked

Dimensional type safety is only implemented at the Quantity level, due to the usage of @always_inline('builtin') throughout Dimensions, Dimension, Ratio and Angle to keep compile times fast, and there is currently no way of adding additional contraints to methods that use that decorator. The result is expressions like these are erroneously allowed.

comptime BadUnit = Meter.D * Mile.D # Bad

var a = Meter(10) * Mile(10) # Reject properly due to using Quantity

You are still protected from surprising behavior when doing actual calculations, but care must be taken when defining new units.

Integer Rounding

When using integer value representations, operations are subject to integer rounding rules. If precision is important, please use a float point representation

comptime IntSeconds = Second[DType.int64]

var a = IntSeconds(11) / IntSeconds(3) # returns IntSeconds(3)

About

A powerful, type-safe dimensional analysis library in Mojo

Resources

License

Stars

Watchers

Forks

Sponsor this project

Contributors 3

  •  
  •  
  •