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
use lib '../lib';
use Physics::Measure :ALL;
# Basic mechanics example (SI units)...
# Define a distance and a time
my \d = 42m; say ~d; #42 m (Length)
my \t = 10s; say ~t; #10 s (Time)
# Calculate speed and acceleration
my \u = d / t; say ~u; #4.2 m/s (Speed)
my \a = u / t; say ~a; #0.42 m/s^2 (Acceleration)
# Define mass and calculate force
my \m = 25kg; say ~m; #25 kg (Mass)
my \f = m * a; say ~f; #10.5 N (Force)
# Calculate final speed and distance travelled
my \v = u + a*t; say ~v; #8.4 m/s (Speed)
my \s = u*t + (1/2) * a * t*t; say ~s; #63 m (Length)
# Calculate potential energy
my \pe = f * s; say ~pe; #661.5 J (Energy)
# Calculate kinetic energy
my \ke1 = (1/2) * m * u*u;
my \ke2 = (1/2) * m * v*v;
# Calculate the delta in kinetic energy
my \Δke = ke2 - ke1;
# Compare potential vs. delta kinetic energy
(pe cmp Δke).say; #Same
This example shows some key features of Physics::Measure...
- support for SI prefixes, base and derived units (cm, kg, ml and so on)
- imported as raku postfix operators for convenience and clarity
- custom math operators
(+-*/)
for easy inclusion in calculations - inference of type class (Length, Time, Mass, etc.) from units
- derivation of type class of results (Speed, Acceleration, etc.)
Use Cases
The Physics::Measure and Physics::Unit modules were designed with the following use cases in mind:
- convenient use for practical science - lab calculations and interactive presentation of data sets
- a framework for educators and students to interactively explore basic physics via a modern OO language
- an everyday unit conversion and calculation tool (not much point if it can't convert miles to km and so on)
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:
- a raku Class and Object model to represent Units and Measurements
- methods for Measure math operations, output, comparison, conversion, normalisation and rebasing
- a Unit Grammar to parse unit expressions and cope with textual variants such as ‘miles per hour’ or ‘mph’, or ‘m/s’, ‘ms^-1’, ‘m.s-1’
- an extensible library of about 800 built in unit types covering SI base, derived, US and imperial
- a set of "API" options to meet a variety of consumption needs
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: Libra Shorthand 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 libra ♎️ 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)
#...there is an ASCII variant of <♎️> namely <libra>
Use the emoji editor provided on your system (or just cut and paste)
#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 $dsdt = $s / $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
Unit Conversion
#Unit Conversion uses the .in() method - 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.WHAT; #(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:
our %type-hints = %(
Area => <Area FuelConsumption>,
Energy => <Energy Torque>,
Momentum => <Momentum Impulse>,
Frequency => <Frequency Radioactivity>,
);
To adjust this, you can delete the built in key and replace it with your own:
my %th := %Physics::Unit::type-hints;
#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:
GetMeaUnit('nmile').NewType('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> )
}
}
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 p6steve / via the github Issues above.