raku Physics::Unit vs. Python Pint

[Health warning – the following is written by the author of raku Physics::Unit & Physics::Measure – so it is an opinion piece with a strong pro-raku Physics::Measure bias…]

The raku Physics::Unit and Physics::Measure module family (“rPM”) was built to make the most of raku’s unique blend of innovative programming language features. During the build, I had the opportunity to use a lot of raku capabilities and principles, both within the module code and in the way it is used.

These raku modules took inspiration from the perl5 Physics::Unit module on cpan written by Joel Berger, in particular the smart handling of type derivations in math operations and parsing, but otherwise were written ‘blind’ without reference to other languages’ support for units of measurement.

Now that the first wave of build is done for raku Physics::Measure, it is time to take stock:

  • what did raku bring to the party?
  • is raku better than Python?

I have chosen the Python Pint package to be the “best of breed” Python equivalent of raku Physics::Measure for the comparison examples – by way of a compliment to its authors. My detailed Pint to rPM comparison table is here.

rPM Design Principles

raku is already very good at embedding perl5 and Python packages via the Inline:: modules and, since it is a direct descendant of perl5 (raku was formerly known as perl6), many current raku modules are straight translations from perl5. This is not the case with Physics::Measure which was designed and built from scratch with raku.

Here is what I set out to achieve…

  • Units, Errors & Measures: class types to smoothly extend Raku OO
  • Rat math (round trip fidelity): 312.15 K <=> 39 ºC
  • Natural unicode: my \ν = c / λ;
  • Postfix notation: my \λ = 2.5nm;
  • Grammar notation: my $p = ♎️ ’25 kg m^2 / s^3’; #25J
  • SI and US/Imperial conversions: my $f = $l.in: <ft>;
  • Errors control rounding: my $val1 = 2.8275793cm ±5%; #2.8276cm ±0.141

Since my scoring “system” is focused on “what did raku bring to the party”, it is highly subjective, totally one sided and biased. But hopefully -Ofun!

Overall it awards Python Pint 30/70 whereas raku Physics::Measure wins with 62/70.

See down below for the blow-by-blow comparison…

My Conclusion

After performing this comparison, I feel that raku Physics::Measure offers a substantial step forward in code clarity, readability and maintainability over Python Pint for anyone who wants to use units of measurement for educational, engineering or scientific problems.

This example typifies what I mean:

raku helps in three distinct ways

 – language features such as Grammars and Class Types were used to develop the rPM modules. In comparison Pint has had to implement these functions without similar Python core support

– particularly in the case of the lack of core Type constructs in Python, this limits the usability and type safety of Pint in comparison to rPM

– language features such as unicode, Rats and postfix operators help to make the rPM modules more usable in the problem domain and a more natural language extension (or slang)

So…:

– yes raku brings a lot to the party

– yes raku is better than Python

Do I think that these local improvements in the “units of measurement domain” are sufficient to convince scientific programmers to switch to raku to avoid the limitations of Python? No. 

Do I think that raku is a next generation language with a wide range of small, but significant improvements that, put together, amount to a real step change in the toolkit available to scientific programmers? Yes. 

Do I think that raku is worth (another) look by anyone who feels constrained by Python’s emphasis on conformity? Yes.

Please do provide your feedback and comments…

~p6steve

Units, Errors & Measures [Python Pint 5/10, rPM 8/10]

The family of four modules – Physics::Measure, Physics::Unit, Physics::Error and Physics::Constants extend the core raku object model like this.

At the highest level, in raku code terms this looks like…

At face value this is a vast improvement in consistency and type safety versus Python – however, Pint has recognised the need for Quantity Types and implemented them at the module level.

So, many practical benefits of Raku types are also there in Python Pint

  • detect the Measure type from the Unit definition String
  • derive the Measure type from operations Speed = Distance / Time
  • prevent incorrect operations Distance + Time

But … on scratching the surface, it becomes clear that there are many disadvantages of handling types in the Pint module:

  • raku variable Types prevent many more errors e.g. my Speed $s = $t / $d;
  • raku language constructs make the wider use of Types very natural

These raku examples (there are many more) have no direct equivalent in Python / Pint:

given $measurement {
    when Length { say 'long' }
    when Time    { say 'long' }
    when Speed   { say 'fast'  }
}

– or –

subset Limited of Speed where 0 <= *.in('mph') <= 70;

Rat math (round trip fidelity) [Python Pint 1/10, rPM 10/10]

This core raku feature is a slam dunk vs. Python. You will also note that rPM automagically knows that kilometer2 meter is m^3 (Volume).

thickness = 68 * ureg.m
area = 60 * ureg.km**2
n2g = 0.5 * ureg.dimensionless 
phi = 0.2
sat = 0.7  

volume = area * thickness * n2g * phi * sat

285.59999999999997 kilometer2 meter
my \thickness = 68m;
my \area = ♎️ '60 sq km';
my \n2g = ♎️ '0.5 ①';
my \φ = 0.2;
my \sat = 0.7;

my \volume = area * thickness * n2g * φ * sat;

285600000m^3

Natural Unicode [Python Pint 0/10, rPM 10/10]

The rPM example below is from the Physics::Constants synopsis here … there are many other rPM areas where unicode is cool like my \θ1 = ♎️ <45°30′30″>;  #45°30′30″ .

Postfix notation [Python Pint 0/10, rPM 10/10]

Mathematicians and Physicists have spent centuries applying the art of concision to concepts in order to help humans think at a higher level. This example clearly shows the potential for raku to declutter scientific code and improve perspicacity.

Grammar notation [Python Pint 7/10, rPM 9/10]

I thought this would be a big win for rPM … but it turns out that using a raku Grammar to parse Unit strings is matched by the functionality of the Pint string parser.

Both handle a similar input range:

  • SI, US and Imperial
  • Prefix & Unit name
  • Abbreviations & Plurals
  • Power symbols ^ | ** | ²

rPM output defaults to the abbreviated initial(s)

rPM goes beyond Pint by knowing that “kg m^2/s^2” is the same as “J” and automatically applies the SI Derived Unit relationship (so my Pint score is off a couple of pips)

Pint does this…

> m | meter | metre
meter
> ft | foot | feet
foot
> km | kilom | kilometer
kilometer
> J | joule | joules
joule
> kg m^2 | s^2 | kg m^2/s^2 | kg m**2/s**2 |  kg·m²/s²
kilogram·meter²/second²

rPM does this…

> m | meter | metre
m
> ft | foot | feet
ft
> km | kilom | kilometer
km
> J | joule | joules
J
> kg m^2 | s^2 | kg m^2/s^2 | kg m**2/s**2 |  kg·m²/s²
J

All the same I would still lean on the raku saves 70% post I wrote some time back that shows just how powerful and concise raku Grammars are compared to rolling your own recursive descent parser (in that case compared to perl)

SI and US/Imperial conversions [Python Pint 9/10, rPM 8/10]

Both Python Pint and rPM do a thorough job here. In fact during my study of Python Pint I found that they have better support for the slight difference between beer barrel and oil barrel volume … so an overall win for Pint.

Errors control rounding [Python Pint 8/10, rPM 7/10]

The raku Physics::Error module introspects the precision and relative size of the Error value and uses this to control the rounding of the Physics::Measure value that is output.

my $val1 = 2.8275793cm ±5%;
#2.8276cm ±0.141

my $val2 = 2.8275793cm   #without Error
#2.8275793cm

This is a cool trick that Python Pint does not have. However, Pint uses the Python uncertainties module that has more stats oriented standard deviation uncertainty propagation whereas raku Physics::Error is a more basic single value model. So, for now, Pint has the edge on this until the raku eco-system catches up…

And finally…

And, for the record here are the raku Type Classes implemented: