Rand Stats

Physics::Measure

zef:librasteve

THIS SOFTWARE IS PROVDED "AS IS" WITHOUT WARRANTY OR LIABILITY.

THIS SOFTWARE IS NOT INTENDED FOR USE IN MISSION CRITICAL APPLICATIONS

Version 2+

Note: The caret prefix ^ can now used as an alternative to the libra prefix ♎️ to ease typing. Also the tilde ~ has been added as an alternative to ± to introduce an Error term.

This version of Physics::Measure has been adapted to work with the new Physics::Unit:ver<2+>:api<2> release.

raku-Physics-Measure

Provides Measure objects that have value, units and error and can be used in many common physics calculations. Uses Physics::Unit and Physics::Error.

Instructions

zef --verbose install Physics::Measure

and, conversely, zef uninstall Physics::Measure

Synopsis

#!/usr/bin/env raku
use lib '../lib';
use Physics::Measure :ALL;

#see https://github.com/librasteve/raku-Physics-Measure/blob/master/README.md for explanation

my \d = 42m;				say ~d;		#42m
my \t = 10s;				say ~t;		#10s

my \u = d ÷ t;				say ~u;		#4.2m/s
my \a = u ÷ t;				say ~a;		#0.42m/s^2

my \m = 25kg;				say ~m;		#25kg
my \f = m × a;				say ~f;		#10.5N

my \v = u + a×t;			say ~v;		#8.4m/s
my \s = u×t + (1/2) × a × t²;	say ~s;	#63m

my \pe = f × s;				say ~pe;	#661.5J

my \ke1 = (1/2) × m × u²;
my \ke2 = (1/2) × m × v²;

my \Δke = ke2 - ke1;
(pe cmp Δke).say;                      #Same

This example shows some key features of Physics::Measure...

Use Cases

The Physics::Measure and Physics::Unit modules were designed with the following use cases in mind:

Some other use cases - use for physical chemistry calculations (eg. environmental) and for historic data ingestion - have also been seen on the horizon and the author would be happy to work with you to help adapt to meet a wider set of needs.

Design Model

To address the Use Cases, the following consistent functional parts have been created:

Together Physics::Measure and Physics::Unit follow this high level class design model:

class Unit {
   has Str $.defn;
   #... 
}
class Measure {
   has Real $.value;
   has Unit $.units;
   has Error $.error;
   #...
}
class Length is Measure {}
class Time   is Measure {}
class Speed  is Measure {}
#and so on

Type classes represent physical measurement such as (Length), (Time), (Speed), etc. They are child classes of the (Measure) parent class.

You can do math operations on (Measure) objects - (Length) can add/subtract to (Length), (Time) can add/subtract to (Time), and so on. A mismatch like adding a (Length) to a (Time) gives a raku Type error cannot convert in to different type Length. You can, however, divide e.g. (Length) by (Time) and then get a (Speed) type back. (Length) ** 2 => (Area). (Length) ** 3 => (Volume). And so on. There are 36 pre-defined types provided. Methods are provided to create custom units and types.

Therefore, in the normal course, please make your objects as instances of the Child type classes.

Three Consumer Options

Option 1: Postfix Operator Syntax (SI Units)

As seen above, if you just want SI prefixes, base and derived units (cm, kg, ml and so on), the :ALL export label provides them as raku postfix:<> custom operators. This option is intended for scientist / coders who want fast and concise access to a modern Unit library. Here is another example, basic wave mechanics, bringing in the Physics::Constants module:

use Physics::Constants;  #<== must use before Physics::Measure 
use Physics::Measure :ALL;

$Physics::Measure::round-to = 0.01;

my= 2.5nm; 
my= c / λ;  
my \Ep =* ν;  

say "Wavelength of photon (λ) is " ~λ;              #2.5 nm
say "Frequency of photon (ν) is " ~ν.norm;          #119.92 petahertz 
say "Energy of photon (Ep) is " ~Ep.norm;           #79.46 attojoule

The following SI units are provided in all Prefix-Unit combinations:

SI Base Unit (7)SI Derived Unit (20)SI Prefix (20)
'm', 'metre','Hz', 'hertz','da', 'deka',
'g', 'gram','N', 'newton','h', 'hecto',
's', 'second','Pa', 'pascal','k', 'kilo',
'A', 'amp','J', 'joule','M', 'mega',
'K', 'kelvin','W', 'watt','G', 'giga',
'mol', 'mol','C', 'coulomb','T', 'tera',
'cd', 'candela','V', 'volt','P', 'peta',
'F', 'farad','E', 'exa',
'Ω', 'ohm','Z', 'zetta',
'S', 'siemens','Y', 'yotta',
'Wb', 'weber','d', 'deci',
'T', 'tesla','c', 'centi',
'H', 'henry','m', 'milli',
'lm', 'lumen','μ', 'micro',
'lx', 'lux','n', 'nano',
'Bq', 'becquerel','p', 'pico',
'Gy', 'gray','f', 'femto',
'Sv', 'sievert','a', 'atto',
'kat', 'katal','z', 'zepto',
'l', 'litre','y', 'yocto',

#litre included due to common use of ml, dl, etc.

Option 2: Object Constructor Syntax

In addition to the SI units listed above, Physics::Measure (and Physics::Unit) offers a comprehensive library of non-metric units. US units and Imperial units include feet, miles, knots, hours, chains, tons and over 200 more. The non-metric units are not exposed as postfix operators.

my Length $d = Length.new(value => 42, units => 'miles');   say ~$d;         #42 mile
my Time   $t = Time.new(  value =>  7, units => 'hours');   say ~$t;         #7 hr
my $s = $d / $t;                                  say ~$s in 'mph';          #6 mph

A flexible unit expression parser is included to cope with textual variants such as ‘miles per hour’ or ‘mph’; or ‘m/s’, ‘ms^-1’, ‘m.s-1’ (the SI derived unit representation) or ‘m⋅s⁻¹’ (the SI recommended string representation, with superscript powers). The unit expression parser decodes a valid unit string into its roots, extracting unit dimensions and inferring the appropriate type.

#Colloquial terms or unicode superscripts can be used for powers in unit declarations 
    #square, sq, squared, cubic, cubed
    #x¹ x² x³ x⁴ and x⁻¹ x⁻² x⁻³ x⁻⁴

Of course, the standard raku object constructor syntax may be used for SI units too:

my Length $l = Length.new(value => 42, units => 'μm'); say ~$l; #42 micrometre

This syntax option is the most structured and raku native. For example, it helps educators to use units and basic physics exercises as a way to introduce students to formal raku Object Orientation principles.

Option 3: Caret Prefix Syntax

In many cases, coders will want the flexibility of the unit expression parser and the wider range of non-metric units but they also want a concise notation. In this case, the unicode libra emoji ^ is provided as raku prefix for object construction. The subject can be enclosed in single quotes ^'', double quotes ^"" of (from v1.0.10) angle brackets ^<>. Separate the number from the units with a space.

#The caret ^ is shorthand to construct objects...
    my $a = ^<4.3 m>;                  say "$a";		#4.3 m
    my $b = ^<5e1 m>;                  say "$b";		#50 m
    my $c = $a;                          say "$c";		#4.3 m
    my Length $l = ^ 42;                say "$l";		#42 m (default to base unit of Length)
#About 230 built in units are included, for example...
    my $v2 = ^<7 yards^3>;          #7 yard^3         (Volume)
    my $v3 = $v2 in 'm3';           #5.352 m^3        (Volume) 
    my $t  = ^<3 s>;                #3 s              (Time)
    my $ac = $v3 / $t;              #0.000106438 m/s^2 (Acceleration)
    my $sm = ^<70 mph>;             #70 mph           (Speed)
    my $fo = ^<27 kg m / s2>;       #27 N             (Force)
    my $en = ^<26 kg m^2 / s^2>;    #26 J             (Energy)
    my $po = ^<25 kg m^2 / s^3>;    #25 W             (Power)

Special Measure Types

Angles

#Angles use degrees/minutes/seconds or decimal radians
    my $θ1 = ^<45°30′30″>;      #45°30′30″ (using <> to deconfuse quotation marks)
    my $θ2 = ^<2.141 radians>;  #'2.141 radian'
#NB. The unit name 'rad' is reserved for the unit of radioactive Dose

# Trigonometric functions sin, cos and tan (and arc-x) handle Angles
    my $sine = sin( $θ1 );      #0.7133523847299412
    my $arcsin = asin( $sine, units => '°' ); #45°30′30″
#NB. Provide the units => '°' tag to tell asin you want degrees back

Time

#The Measure of Time has a raku Duration - i.e. the difference between two DateTime Instants:
    my $i1 = DateTime.now;
    my $i2 = DateTime.new( '2020-08-10T14:15:27.26Z' );
    my $i3 = DateTime.new( '2020-08-10T14:15:37.26Z' );
    my Duration $dur = $i3-$i2;

#Here's how to us the libra assignment operator ^ for Time...
    my Time $t1 = ^<5e1 s>';     	#50 s
    my Time $t2 = ^ $dur;        	#10 s
    my $t3 = $t1 + $t2;         	#60 s
    my Time $t4 = ^<2 hours>;   	#2 hr
    $dur = $t4.Duration;            #7200

Currency

#Many popular currencies are provided. Exchange rates are updated on
#Physics::Unit installation. Prefix (& postfix) can be used for the most common
#such as - US$, C$, A$, NZ$, ¥, £ and (€).
    my $m1 = ^<2.141 USD>;              #2.141USD
    my $m2 = US$42;                     #42USD
    my $m3 = £42;                       #42GBP
    say $m3 in '';                     #48.026EUR

Unit Conversion

Physics::Measure exports the term 'in' as an infix operation for unit conversion.

#Unit Conversion uses the in operator - specify the new units as a String
    my Length $df = ^<12.0 feet>;         #12 ft
    my $dm = $df in 'm';                  #3.658 m
       $dm = $df in <m>;                  #alternate form
    my Temperature $deg-c = ^<39 °C>;
    my $deg-k = $deg-c in 'K';            #312.15 K
    my $deg-cr = $deg-k in '°C';          #39 °C

#Use arithmetic to get high order or inverse Unit types such as Area, Volume, Frequency, etc.
    my Area	      $x = $a * $a;           #18.49 m^2
    my Speed      $s = $a / $t2;          #0.43 m/s
    my Frequency  $f = 1  / $t2;          #0.1 Hz

#Use powers & roots with Int or Rat (<1/2>, <1/3> or <1/4>)
    my Volume     $v = $a ** 3;           #79.507 m^3
    my Length	  $d = $v ** <1/3>;       #0.43 m

The ① symbol is used to denote Dimensionless units.

Rounding & Normalisation

#Set rounding precision (or reset with Nil) - does not reduce internal precision
    $Physics::Measure::round-to = 0.01;
#Normalize SI Units to the best SI prefix (from example above)
    say "Frequency of photon (ν) is " ~ν.norm;    #119.92 petahertz
#Reset to SI base type with the .rebase() method
    my $v4 = $v2.rebase;                  #5.35 m^3

Comparison Methods

#Measures can be compared with $a cmp $b
    my $af = $a in 'feet';              #4.3 m => 14.108 feet
    say $af cmp $a;                     #Same
#Measures can be tested for equality with Numeric ==,!=
    say $af == $a;                      #True
    say $af != $a;                      #False
#Use string equality eq,ne to distinguish different units with same type  
    say $af eq $a;                      #False
    say $af ne $a;                      #True

Output Methods

To see what you have got, then go:

my $po = 25W;   
say ~$po; say "$po"; say $po.Str;       #25 W  (defaults to derived unit)
say +$po; say $po.value; say $po.Real;  #25 
say $po.^name;                          #(Power)
say $po.canonical;                      #25 m2.s-3.kg   (SI base units)
say $po.pretty;                         #25 m²⋅s⁻³⋅kg   (SI recommended style)
                                              ^...^......unicode Dot Operator U+22C5

Dealing with Ambiguous Types

In a small number of case, the same units are used by different unit Types. Type hints steer type inference:

has %.type-hint = %(
    Area           => <Area FuelConsumption>,
    Energy         => <Energy Torque>,
    Momentum       => <Momentum Impulse>,
    Frequency      => <Frequency Radioactivity>,
    SpecificEnergy => <SpecificEnergy Dose>,
);

To adjust this, you can delete the built in key and replace it with your own:

my %th := Unit.type-hint;

#default type-hints
my $en1 = ^'60 J';     #'$en1 ~~ Energy';
my $tq1 = ^'5 Nm';     #'$tq1 ~~ Torque';

#altered type-hints
%th<Energy>:delete;
%th<Torque> = <Energy Torque>;

my $fo3 = ^'7.2 N';
my $le2 = ^'2.2 m';
my $tq2 = $fo3 * $le2;  #'$tq2 ~~ Torque';

Custom Measures

To make a custom Measure, you can use this incantation:

Measure.unit-find('nmile').type-bind('Reach');

class Reach is Measure {
    has $.units where *.name eq <nm nmile nmiles>.any;

    #| override .in to perform identity 1' (Latitude) == 1 nmile
    method in( Str $s where * eq <Latitude> ) { 
        my $nv = $.value / 60; 
        Latitude.new( value => $nv, compass => <N> )
    }   
}

Precision and Rounding

In general the value of a Measure (and an Error) is stored in the number type that was declared. For example:

3kg;    #Int
0.3kg;  #Rat
3e-2kg  #Num

Measure can work at unlimited precision (value and error stored as type FatRat). Use the caret syntax and express the value (and error if any) in regular Num exponential format. These will be parsed by the FatRatStr module and stored in as FatRats. This is particularly useful with very large or small physical constants where math operations will still correctly round trip.

say  0.1kg + 0.2kg == 0.3kg;                  #True
say  1e-1kg + 2e-1kg == 3e-1kg;               #False
say  ^<1e-1 kg> + ^<2e-1 kg> == ^<3e-1 kg>;   #True

Output Rounding

Measure output is rounded when .Str is called. This does not affect stored value (and error) precision or type for subsequent operations. Num and FatRat values (and errors) are rendered in (unlimited precision) Num format for ease of reading.

Small Values

Small values can be specified as Num or FatRat types.

Num is what you get with the postfix operator syntax if the value has an exponent.

9.1093837015e-31kg ±0.0000000028e-31       \   are the same
9.1093837015e-31kg ±2.8e-40                /

9.1093837015e-31kg ±0.0000000028e-31       \   look the same
9.1093837015e-3122222kg ±0.0000000028e-31  /

FatRat is what you get when the Measure is specified as a String via the FatRatStr module.

^<9.1093837015e-31 kg ±0.0000000028e-31> also the same

all give => 9.1093837015e-31kg ±0.0000000028e-31

Large Values

Similar rules apply to large numbers.

e.g. 9109_383_7015e30kg ±28e30 gives 9.1093837015e+40kg ±0.0000000028e40

Try .norm to get 91093837015000Qg ±28000

Modest Values

Measure considers values in the range 0.000_1 < * <10_000 to be modest and outputs these as decimal Rats regardless of the way they were specified and stored. Errors are output in the same format. This cut off can be set via $Physics::Measure::round-cut.

Operations

Math operations always try to maintain exact precision. Default is to produce a FatRat as the type of the value of the result. Where the value is irrational, this will fallback to a Num.

say  0.1kg + 0.2kg == 0.3kg;                  #True
say  1e-1kg + 2e-1kg == 3e-1kg;               #False
say  ^<1e-1 kg> + ^<2e-1 kg> == ^<3e-1 kg>;   #True

Unlimited Precision

The smallest officially accepted SI prefix today is quecto- (symbol q), 1e-30 it reaches the 30th decimal place. Num precision is about 17 decimal digits - so Physics::Measure defaults to FatRat numeric type for .value and .error.absolute. This provides practical calculations with decimal exponents up to many hundreds in value and maintains round trip calculations.

Error Controlled

The precision of the output is controlled by the significant digits of the error. To select this mode, set $Physics::Measure::round-val = Nil; (the default).

Setting Controlled

The precision of the output is controlled by a setting, to selct this mode, give it a value e.g. $Physics::Measure::round-val = 0.01;

Hints

Sometimes unlimited precision can get out of hand - a setting of $Physics::Measure::round-val = 0.000000000000001; is 16 digits ~ just at the limit of Num precision.

The value will usually round trip, however the error may float around a little (geddit?) if units are changed. <=> (compare Numeric) and =~= (approx.equal) are your friends.

Summary

The family of Physics::Measure, Physics::Unit and Physics::Constants raku modules is a consistent and extensible toolkit intended for science and education. It provides a comprehensive library of both metric (SI) and non-metric units, it is built on a Type Object foundation, it has a unit expression Grammar and implements math, conversion and comparison methods.

Any feedback is welcome to librasteve / via the github Issues above.

Copyright (c) Henley Cloud Consulting Ltd. 2021-2025

Copyright (c) Stephen Roe 2026