I second the appreciation of the lib name, and can relate to the "I programmed Pint to scratch my own itches" line, but how does it compare to Sympy?
I feel like when you must deal with units, integration with other mathematical tools is indispensable. So while I guess numpy integration is more than good enough on that front, is this lib's api enough of an improvement over Sympy's to forego all the niceties the latter incorporates?
It's nice to have as small dependencies as possible. If Pint provides everything your project needs, buying into the complete Sympy ecosystem is just a complexity cost.
Having used sympy for rather simple task I wonder how much cost sympy really adds. Can you elaborate? It seems to me that symbolic evaluation and unit conversuin are not too far apart and buying into this ecosystem was never a big tech debt in my eyes. (Unlike maybe scipy or numpy)
Every dependency adds complexity to your project, you might never notice it because the abstractions are water tight (for your use-case) but it’s there.
Dependency free: it depends only on Python and its standard library. It interacts with other packages like numpy and uncertainties if they are installed
Oh I guess I looked into some build dependencies because there were actually none . This is actually a good thing more libraries should do. So I guess the only downside would be that potentially versioned dependencies/breaking changes need to be managed one level above if you want to use those parts. Is there actually a good way manage "supported" optional dependencies in python?
Great docs. This would have made an indispensable tool in college. (Also, though, I took physics before I started CS, so I really didn’t know my way around basic programming).
How does this compare with astropy's units module? The usage (after preamble) seems pretty similar: see e.g. https://docs.astropy.org/en/stable/units/index.html. As far as I can see, this part of astropy is by now quite mature (it was already pretty stable when I started grad school 5 years ago)
One thing it supports that does a lot of difference in that regard is dealing with unit conversions which would be "dimentionless" -- such as m3/m3 (i.e.:`volume per volume`) and then converting to `cm3/m3` and keeping the dimension.
i.e.: in pint:
>>> import pint
>>> ureg = pint.UnitRegistry()
>>> m = ureg.meter
>>> v = 1 \* (m\*3)/(m\*3)
>>> v
<Quantity(1.0, 'dimensionless')>
And then, after that (as far as I know), it's not really possible to do additional unit conversions properly knowing that it was m3/m3.
In barril:
>>> from barril.units import Scalar
>>> a = Scalar(3, 'm3/m3')
>>> a.GetValue('cm3/m3')
3000000.0
>>> a.category
'volume per volume'
>>> a.unit
'm3/m3'
and something as `a.GetValue('m3')` (with an invalid value) would give an error saying that the conversion is actually invalid.
The unit database (which was initially based on the POSC Units of Measure Dictionary) is a bit more tailored for the Oil & Gas field, but should be usable outside of it too.
Thanks for pointing this out. I was excited to give barril a test because I am a regular user of pint. I'm going to be a bit negative about barril below, but I'm doing it in case other people are looking an interested in a comparison.
First off, you can definitely do conversions between different dimensionless quantities:
from pint import Quantity
a = Quantity(3, 'm^3/m^3')
a.to('cm^3/m^3')
does what you'd expect. There's no problem with converting between different "dimensionless" units.
I don't really understand what the "category" is, though, so maybe I'm missing the point? I notice that some units can be in multiple categories, e.g. "J" can be a "moment of force" or an "energy", but I can still do:
a = Scalar(5.0, 'J', 'moment of force')
b = Scalar(10.0, 'J', 'energy')
c = a / b
c.category
>>> ''
d = a + b
d.category
>>> 'moment of force'
That seems a bit inscrutable. What is a category and why am I keeping track of it? I think this might be the core of why barril is useful to some people but I don't understand it.
The biggest thing I found playing with barril is that while I can multiply 3 lengths together to get a volume; if I multiply a density by a volume and ask for a mass it throws an exception. Like they've just special-cased some specific conversions rather than doing dimensional analysis? This seems like the core functionality of a units library so I'm a bit confused again if I've missed something.
Internally the library has a "quantity type", which defines for a given quantity all the given conversions that are valid in the database and a "category" which is used so that you can create subsets of units which you want to consider valid for your application.
As a note, it's main use-case is NOT doing computations such as dividing/multiplying Scalars (albeit that's supported it does have some caveats -- so, while it also does dimensional analysis, it's really not the core functionality), rather the core functionality is making the conversions based on what's available in the unit database (so, you're not supposed to be creating units out of the unit database, rather, you want to collect input from the units you support and then make your conversions to other units you still support -- think of doing validation of input, converting to values in units expected by the user, defining a unit-system for input, converting to your internal unit-system for actual computation in C/C++, ...).
So, I guess it depends on what you consider as core for a units library. As I mentioned, it's created for an Oil & Gas use-case -- albeit it's also used in other engineering-related projects -- where the units are all pretty much well mapped in the application and you're worried that you are getting into units you're not expecting and that'd actually be an error.
p.s.: if you have specific use-cases which don't work as you expect, I suggest contacting the library maintainers -- once that was me, but it's been a while ;)
p.s.: thanks for letting me know about how to deal with dimensionless units in pint (I wasn't aware of that).
Hm, that's very interesting, thanks. I think I'm approaching this from a physics perspective, where units and dimensional analysis are very important, and there's a very specific formalism and philosophy involved. For example, dimensionless quantities are deeply meaningful -- see the Buckingham pi theorem for example.
On the other hand, I think this library seems very pragmatic: It doesn't match my "rigorous" ideas about units but that's fine because it's actually trying to do something else.
As I understand it, the idea is that I can't /necessarily/ give a quantity of "250 mm" to a function that expects a length because, for example, the quantity might be of category "rod length" and the function expects a "cylinder width". The /units/ match, but the /category/ does not.
That's very interesting and I see why it's useful. It strikes me as trying to be something like a type system, with pre-defined types for physical units that you "subclass" or "parameterize". I think this is something I will think about quite a bit more.
I should also thank you because (over) thinking about this has already led me to discover the idea of "parametric units":
I did some code for an O&G company a couple of years ago and was reasonably happy with pint,but barril looks like it might have been even better. The handling of dimensionless stuff like volume by volume was a point I was never quite happy with in pint.
As another commenter said, renaming the symbol “ureg” to “units” would improve readability. I am not sure if I would ever use this (the code for unit conversion in my ancient, not touched in a decade, recipe site cookingspace.com for conversions was very simple). However, I spent ten minutes looking at the source code and realized I should improve my Python-Fu. I use Python a lot for deep learning and NLP work but I seldom use Python in the way I use Lisp languages. I might enjoy Python more if I took a deeper dive.
This numpy example was confusing to me because of the digit reuse; it looked to me like the second quantity should have been in metres. I think something like this would have been better:
A question I always ask is how well it handles regional differences in imperial units for instance UK and US have significantly different quantities [0]
I’d be presuming this library caters mainly to the US variant?
Plus it also looks like the author has thought this through in a very comprehensive manner:
* Using contexts for unit redefinition
The exact definition of a unit of measure can change slightly depending on the country, year, and more in general convention. For example, the ISO board released over the years several revisions of its whitepapers, which subtly change the value of some of the more obscure units. And as soon as one steps out of the SI system and starts wandering into imperial and colonial measuring systems, the same unit may start being defined slightly differently every time - with no clear ‘right’ or ‘wrong’ definition.
The default pint definitions file (default_en.txt) tries to mitigate the problem by offering multiple variants of the same unit by calling them with different names; for example, one will find multiple definitions of a “BTU”:
british_thermal_unit = 1055.056 * joule = Btu = BTU = Btu_iso
international_british_thermal_unit = 1e3 * pound / kilogram * degR / kelvin * international_calorie = Btu_it
thermochemical_british_thermal_unit = 1e3 * pound / kilogram * degR / kelvin * calorie = Btu_th
That’s sometimes insufficient, as Wikipedia reports no less than 6 different definitions for BTU, and it’s entirely possible that some companies in the energy sector, or even individual energy contracts, may redefine it to something new entirely, e.g. with a different rounding.
Pint allows changing the definition of a unit within the scope of a context. This allows layering; in the example above, a company may use the global definition of BTU from default_en.txt above, then override it with a customer-specific one in a context, and then override it again with a contract-specific one on top of it.*
> The exact definition of a unit of measure can change slightly depending on the country, year, and more in general convention
How truly wonderful. I’m delighted to see this level of diligence. Too often you see these “clickbait” libraries that seem to do something great but then you scratch the surface and see there’s a lot of stuff not fully thought out.
It's reasonable to say that although strictly SI definitions changed (and will continue to be refined now that they're all in terms of a known constant, our certainty as to the value of these constants will gradually improve) in practice you don't care.
Take the kilogram for example, the definition of the kilogram drifted until the relatively recent decision to define it based on the Planck constant (because previously it used physical Prototypes and those are subject to change over time like any object), but the drift was tiny, less than 30ppm over centuries. If you have an ordinary mass of something, the drift in definition of the kilogram makes far less difference than inaccuracies of your measuring scales, dust settling on the object measured and other factors.
You don't in fact care whether that's a 1885 kilogram of potatoes or a 2021 kilogram of potatoes, because you won't be measuring potatoes to a precision where the difference matters.
In contrast you would presumably care whether you were served a US pint of beer of ~473ml or a UK pint of beer of ~568ml. Quite a difference to a hobbit at least.
Not by default, but Julia has various units packages which integrate fairly well with the rest of the ecosystem. Unitful.jl [1] is the most used one I believe and integrates with DifferentialEquations.jl for example. It should also work with many other things, but you might have to patch it up a bit, especially if operations change the units (such as derivatives).
The Wolfram Language (i.e., the language used for Mathematica) supports units by default through the Quantity object [0], but the software is somewhat expensive for general-purpose use.
There is Frink [0] which was created for the manipulation of physical quantities. I used it extensively in college… imperial units for fluid dynamics? yuck!
I assume it gives you the ability to manipulate the unit registries, work with multiple registries in the same application, etc. Mutable state is one thing, but holding the mutable state in a non-global user-facing object is better than keeping it global and hidden.
Ah yes, named for the one part of the US unit system that actually makes a lot of sense (being powers of 2). I wish we could resize the US pint to 500ml and then have two actually useful systems, just in different bases, with an easy conversion point.
(though I can't actually tell if this is the US pint or the UK pint)
Nah I'm talking about the US unit of volume being related to itself by powers of 2. 16oz to a pint, 8 pints to a gallon. Everything is just halves and doubles. I feel like that's legitimate and convenient. Can't say the same for the rest of the US system.
I think the US pint is something like 476ml (checked; it's 473), and the UK pint is 20 /slightly/ smaller than US oz instead of 16.
I have a wip project that also tries to deal with all physical units and conversions between them. It was quite a lot of fun to figure out (but the current inplementation isn’t great): https://recomputer.io
https://docs.sympy.org/latest/modules/physics/units/index.ht...
(edit: I'm very biased; Sympy's docs and code gave me a lot of fundamental mathematical intuitions -in python- that my education hasn't.)