New version of the units package v0.12

Hello everybody,

Here is a new version of the units pack (v0.12) with these new features:

New features

  1. semi-automatic complete export of all quantities and units of the original mp-units library.
    • this means the following units system are available: si, international, usc, imperial and others
  2. automatic use of clpBNR for arithmetic if expression is not sufficiently ground
  3. pure quantities and units handling with constraints !
  4. quantity_point support for correct handling of things like temperatures or timestamps

1. semi-automatic export of quantities and units

This increase drastically[1] the amount of units and quantities.
This export from the original mp-units library was done through a godforsaken hacky script where I used regex to modify the original c++ code so that it could be fully parsed by prolog.
Then I serialized these terms into the correct prolog facts format.
It was quite a lot of fun ^^

Interestingly, the original library also defines constants as units, so we can do things like this:

?- qeval(X is solar_mass / 'Earth_mass' in 1).
X = 332842.89109838975 * kind_of(1)[1].

3. pure quantities and units

This means that you can do arithmetics with units you don’t know yet.
Let’s take the original speed example:

avg_speed(Distance, Time, Speed) :-
   qeval(Speed is Distance / Time as isq:speed).

We can then compute a unit aware speed from a distance and time:

?- avg_speed(metre, second, Speed).
Speed = 1 * isq:speed[si:metre/si:second].

Through the automatic use of clpBNR and pure handling of units, we can do the reverse, e.g. compute the distance given a speed and time:

?- avg_speed(quantity D, second, metre/second).
D = 1 * isq:length[si:metre].

However, in this case, since D is not on the right hand side of an is, we need to explicitly mark it as a quantity (with a value and unit).

And with the most general query:

?- avg_speed(quantity Distance, quantity Time, Speed).
Distance = _A * isq:length[_B],
Time = _C * isq:time[_D],
Speed = _E * isq:speed[_B/_D],
%... lots of constraints
% you can then specify your units after the expression
?- avg_speed(quantity Distance, quantity Time, Speed), qeval(Speed =:= m/s).
Distance = _A * isq:length[si:metre],
Time = _B * isq:time[si:second],
Speed = 1 * isq:speed[si:metre/si:second],
% clpBNR constraints...

I decided to make the whole system as greedy as possible so that units propagates as quickly as possible. I thought this was to safest route to be sure that all computations are correct.

4. Temperature support

The original library supports temperature through a very neat formulation which is the quantity_point (and more generally the affine space).
Basically, a “quantity” is a displacement vector while a “quantity point” is a quantity with an origin.
Some units like si:kelvin, si:degree_Celsius or usc:degree_Fahrenheit have different predefined origins, that respectively represents the absolute zero, the ice point and the zero for degree fahrenheit:

% origins can be used in expression through addition
?- qeval(RoomTemp is si:ice_point + 21*degree_Celsius).
RoomTemp = qp{o:si:ice_point, q:21 * kind_of(isq:thermodynamic_temperature)[si:kelvin]}.
% ice_point is defined in kelvin, that's why RoomTemp is in kelvin,
% small bug that should be fixed

?- qeval(RoomTemp is si:ice_point + 21*degree_Celsius),
% you can measure your quantity point from a different origin as a quantity (displacement vector)
qeval(RoomTempF is RoomTemp.quantity_from(usc:zeroth_degree_Fahrenheit) in degree_Fahrenheit).
RoomTemp = qp{o:si:ice_point, q:21 * kind_of(isq:thermodynamic_temperature)[si:kelvin]},
RoomTempF = 349r5 * kind_of(isq:thermodynamic_temperature)[usc:degree_Fahrenheit].

Using quantity points is actually about operation you can not do.
You can not add the quantity point 21 degree Celsius from the ice point with itself:

120 ?- qeval(RoomTemp is si:ice_point + 21*degree_Celsius in degree_Celsius), qeval(_ is RoomTemp + RoomTemp).
ERROR: No rule matches units:comparable(+,qp:qp{o:si:ice_point,q:21 * kind_of(isq:thermodynamic_temperature)[si:degree_Celsius
]},qp:qp{o:si:ice_point,q:21 * kind_of(isq:thermodynamic_temperature)[si:degree_Celsius]},_22122)
...
% Execution Aborted

You can actually define origins for any units if you want.
Here is a temperature controller example that shows how this features works in more details.


  1. Beware that this also increase the likelyhood of symbol collision ↩︎

2 Likes

It is great to see such comprehensive resources coming available!

I’m seeing the value in something like this, but in trying out my own examples (pending better documentation), I get somewhat confused. I think this is because things are not what they appear to be. For example:

?- qeval(Speed is metre / second ).
Speed = 1 * kind_of(isq:length/isq:time)[si:metre/si:second].

The answer goal would suggest that Speed is a term with functor *; however it’s really a dictionary:

?- qeval(Speed is metre / second ),write_canonical(Speed).
q{q:kind_of(/(:(isq,length),:(isq,time))),u: /(:(si,metre),:(si,second)),v:1}
Speed = 1 * kind_of(isq:length/isq:time)[si:metre/si:second].

so things like this don’t work:

?-  qeval(Speed is metre / second ), Speed = Value*Units.
false.

Now according to SWI-Prolog -- Executing a query ,

Note that the answer written by Prolog is a valid Prolog program that, when executed, produces the same set of answers as the original program.

In this example the answer goal is
Speed = 1 * kind_of(isq:length/isq:time)[si:metre/si:second]
which will not produce the same answer as the original program, i.e., a dict.

I think this is the sort of thing that will put potential users off. Maybe I’m different, but I use the top level to “probe” any library I’m using to see if the my expectations of the API are as documented; if they don’t align it adds an impediment to using that library.

I also think your focus on performance (e.g., competing with the C++ version) may be premature. Having a simple, clean, documented API should be the priority IMHO.

Thank you so much for your feedback !

So, yes, I have clearly broken this rule. But only to some extent.
Note that I have taken care of the fact that if you stick the answer 1 * kind_of(isq:length/isq:time)[si:metre/si:second] back into qeval, it works:

?- qeval(Speed is metre / second ),
   qeval(Speed =:= 1 * kind_of(isq:length/isq:time)[si:metre/si:second]).
Speed = 1 * kind_of(isq:length/isq:time)[si:metre/si:second].

My rational for that was to have better readability of resulting quantities.
From your second query with write_canonical, you can see how hard it would be to interpret the canonical representation from the dict and all that.
That’s why, I have installed a portraying hook, in order to be simpler to read.
I thought it would have been too tedious to require the user to systematically use an explicit formatting predicate (which I called qformat in the library) to be able to read it.

I also wanted to keep the implementation format for quantities a sort of private structure.
Something that the user of the library should not poke inside.
I’m not even sure that using a dict was a good idea, so I want the freedom to be able to change the implementation structure in the future.

I thought it was an acceptable compromise, but if anyone has a better idea on how to keep the rule while being readable, let me know.

Well, we can’t compete with c++, that’s never ever crossed my mind.
But I thought it would be neat to add partial evaluation (which is one of prolog strength, or is it ?) to get rid of any superfluous computation at runtime.

Anyway, you are right in that I seriously need to better document the library. I should do that right now.

This makes me think that maybe I could portray Speed as qeval(Speed =:= 1 * kind_of(isq:length/isq:time)[si:metre/si:second]) ?
But swi-prolog portray/1 hook only allows me to format the right hand side of Speed = <myformatting>.
Anybody knows if this is possible ?

Interestingly, clpBNR is able to do things like this:

?- X::real(0, 1).
X::real(0, 1).

But I think it is possible because X is an attributed variable.

Just an addition to this, if you want to separate the value and unit through a kind of unification like above, you currently have to do this:

?- qeval(Speed is metre / second), qeval(Speed =:= Value*u:Units).
Speed = 1 * kind_of(isq:length/isq:time)[si:metre/si:second],
Value = 1,
Units = si:metre/si:second.

For now, because I assume by default that an unbound unqualified variable is a value, the expression Value*Units is actually a dimensionless (or unitless) expression.
If you want to indicate that a variable is to be a unit, you can qualify it with u:MyUnit.
Same for the quantity part q:MyQuantity.
Finally, if you use an unbound variable to contain the full q{q:, u:, v:} dict structure, you have to prefix it with quantity MyQuantityDict if it is not used on the left hand side of is.
So to illustrate:

?- qeval((quantity Speed =:= metre / second, Speed =:= V * q:Q[u:U])).
Speed = 1 * kind_of(isq:length/isq:time)[si:metre/si:second],
V = 1,
Q = kind_of(isq:length/isq:time),
U = si:metre/si:second.

So only a little broken? The rule says executing the answer Goal should produce the same value as the original query; so not the same as using the right hand side of an answer goal as an argument to some predicate.

So perhaps a dictionary isn’t the best choice for the API. That’s not to say you can’t use a dict as an internal representation, if that makes sense, but the exported API should be user friendly. Of course that entails mapping to/from the exported and internal representation on each call, so this may not be so “performance friendly”.

You already seem to have a simple representation for specifying literal quantities, e.g., 100 * metre/second; why isn’t that a good representation for exported quantities as well? Now you might lose some information in the process (kind, system, ..) but is that a normal use case? Perhaps my mental model is just too simple, but I’m not sure when I specify a units value as metre/second I get back kind_of(isq:length/isq:time)[si:metre/si:second].

Perhaps more complicated use cases can be supported via a separate predicate to map simple units to an “augmented” form, e.g.,
unit_info(metre/second, units(si:metre/si:second), kind(isq:length/isq:time))
Indeed, you may have partial support for this already, e.g., unit_kind/2.

But it would be unfortunate if that (“change the implementation structure”) caused the external API to change. One of the objectives of a good API is to isolate the user from such things.

I think that would educational. If the API is hard to document, that may indicate where improving the design might help.

That may be a bit difficult. The hook prolog:expand_answer/3 allows you to rewrite variable bindings, so you could use that instead of the portray hook. But you want to replace the whole Goal.

Yes, there’s a separate “hook” for attributes to support outputting residual goals. But you have a normal variable which has been unified with (passive) data structure. It’s just that you don’t want to display that data structure because you find it “unreadable”. But I think it’s preferable to sacrifice readability to ensure transparency (and avoid confusion).

Of course this all goes away if the API’s “native” data structure for a quantity was “more readable”.

Or it could be that Value*Units is just a quantity whose units aren’t yet known, but will be at some point in the future, i.e., the quantity is not dimensionless. I think you have another “kind” for unitless quantities:

?- qeval(R is 2).
R = 2 * 1[1].

The apparent use of (module?) qualification is another part of the current package that I find confusing. It sounds a little misguided to me but I’m not sure what the issue is. (Maybe the API documentation will explain it.)

After thinking a lot about this, thank you so much for this suggestion !
It was right under my nose, but I could not see it.
I have pushed a new version with the following fix:

  • resulting quantities have now the form Value*Quantity[Unit].

That means your example now works:

?- qeval(Speed is metre / second ), Speed = Value*Units.
Speed = 1*kind_of(isq:length/isq:time)[si:metre/si:second],
Value = 1,
Units = kind_of(isq:length/isq:time)[si:metre/si:second].

I continue to use dicts for the internal of the libraries because they are very convenient to work with as an implementor.
But I think I was reluctant to let users interact with dicts because of the usual pitfalls around dicts.

Now, it remains that reverting the two statements: Speed = Value*Units, qeval(Speed is metre / second ) does not work, because I can’t know when parsing Value*Units that Units should be a unit and not a value.
Currently, I disambiguate by wrapping the variables with functors like this:

?- Speed = Value * unit Unit, qeval(Speed is metre / second ).
Speed = 1*unit (si:metre/si:second),
Value = 1,
Unit = si:metre/si:second.
?- qeval(quantity Speed =:= metre / second ).
Speed = 1*kind_of(isq:length/isq:time)[si:metre/si:second].

I’m not sure I fully understand your suggestion.
The problem I see is that would introduce nondeterminism during parsing, and that is not an option.
When encountering a variable, I would not know if the variable is a:

  • value
  • unit
  • quantity[unit]
  • value*quantity[unit]
    Those are the 4 possibilities.

First of all, I have removed the use of u: or q: qualifiers.
For units and quantities like si:metre or isq:speed, the original libraries organized them using c++ namespaces, so that users could selectively import the units they wanted.
I thought of doing the same by piggy backing on swi-prolog module system, but I have not yet really implemented this.

Doesn’t this mean the quantity expression language syntax is ambiguous? Isn’t that something you control as part of the design?

One of the sources of ambiguity is using * for both a “quantity” operator (Value*Units) and arithmetic multiplication. There may be different way to address this but you could always assume to it be a quantity (Value*Units) and use parentheses for value expressions, e.g., (5*2)*metre/s. In fact because Units can also involve multiplication of base units, you probably need to parenthesize that as well, e.g., (5*2)*(ft*lb).

Or you could disambiguate by using a different, non-arithmetic operator for quantities, e.g., 5*2 in metre/s or even 5*2[metre/s] (with judicious choice of precedence, e.g., 650, for the [] operator), e.g.,

?- X = 5*2 in ft*lb, X=..L.
X = 5*2 in ft*lb,
L = [in, 5*2, ft*lb].

?- X = 5*2[ft*lb], X=..L.
X = 5*2[ft*lb],
L = [[], [ft*lb], 5*2].

These are just casual suggestions. I don’t fully understand the full quantity expression language you’re trying to support.

Again, Isn’t that because the language itself is ambiguous (overloading * operator)? Wouldn’t parentheses or an alternative quantity syntax as suggested above clean this up?

Yes it is, but only with variables.
Funily, this is a problem unique to prolog because we can manipulate ungrounded variables.
The original mp-units library uses types to model units and quantities, and from what I understand, uses templates (and a whole lot of new stuff like contracts, etc) to support generic expressions.

The use of the multiplication comes from the original library mp-units:

SI Brochure

The value of the quantity is the product of the number and the unit. The space between the number and the unit is regarded as a multiplication sign (just as a space between units implies multiplication).

So basically, there is nothing special that differentiate between units and values.
It’s like manipulating pairs (value-unit) instead of just a value.
A value is unitless (the unitless unit being 1) or 4 =:= 4*unit(1) and a unit is a unit with value 1 metre =:= 1*metre.

About your suggestions, since you used only ground elements, none of your examples are ambiguous and will be correctly parsed by the library.
On my part, I think my use of wrapping variables isn’t too bad.
Isn’t it customary in prolog to wrap things to disambiguate ?
It’s just that I don’t use wrapping when units are ground, leading to not really pure code ?

Also, mp-units and my library complexifies things a bit because we don’t manipulate only units but also quantities, which means that we manipulate triples Value*Quantity[Unit].
But quantity can never be specified alone, they should always have a unit Quantity[Unit] which disambiguate things.

Sounds like you’re motivated to keep the syntax as well as the semantics of mp-units. I would tend to sacrifice syntactic compatibility for a simple clean API but it’s your call. In any case, one can probably support any useful syntax with a wrapper module.

edit Having thought about this a bit more, it’s probably not an unreasonable restriction that the units for any quantity be ground. It’s nice to be able to think about quantities as constrained values in some sense, but that may not be have many (any?) practical applications, and certainly not something that mp-units would support as a compile time C++ library.

It’s just that I find the multiplication syntax quite simple and beautiful.
I think the fact that we can use variables in prolog for anything is more a problem with prolog than a problem with the syntax.

Well, except for pattern matching in expression:

?- qeval(Value * unit(Unit) =:= 3*m in inch).
Value = 15000r127,
Unit = international:inch.

This is only possible if you can use variables for units.
That’s why I have added support variables for units and quantities.
Or if you are using clpBNR for pure arithmetic:

avg_speed(Distance, Time, Speed) :-
    qeval(Speed =:= Distance / Time as isq:speed).
?- avg_speed(metre, second, quantity Speed). %the usual way
Speed = 1*isq:speed[si:metre/si:second].

% had to modify the code for this, currently very slow the first time and not on master
% this is all very buggy, and should not be relied on for now
?- avg_speed(D*unit(Unit), hour, inch/second).
D = 3600,
Unit = usc:inch.

What’s the value in doing this instead of:

?- qeval(Value*inch=:=3*m).
Value = 15000r127.

As for your second example, I’m just trying to do simple things in a simple way - automatic unit conversion and arithmetic/comparison on quantities. So why not:

?- Distance=metre, Time=second, qeval(Speed is Distance / Time).
Distance = metre,
Time = second,
Speed = 1*kind_of(isq:length/isq:time)[si:metre/si:second].

In this case Speed initially is “unitless” but it can be inferred from the RHS of the is. But not sure why the following should be different:

?- Distance=metre, Time=second, qeval(Speed =:= Distance / Time).
ERROR: Domain error: `1' expected, found `kind_of(isq:length/isq:time)'

Here Speed is assumed to be a unitless quantity. But the units could be inferred from the RHS, i.e., there is a units value that would satisfy this equality. I think this is the sort of thing that could be very confusing to a “naive” user, but need to think more on it.

I spent a bit of time doing some conversions from the appendix of an old physics text book I had. Generally worked as expected once I discovered the appropriate units symbol, e.g., lbf for pounds force. But I did find an odd performance anamoly:

?- use_module(library(units)).
% *** clpBNR v0.12.2 ***.
%   Arithmetic global flags will be set to prefer rationals and IEEE continuation values.
true.

?- time(qeval(X*ft*lbf/s =:= 1*watt)).
% 126,278,452 inferences, 20.629 CPU in 21.602 seconds (95% CPU, 6121546 Lips)
X = 2500000000000000r3389544870828501.

?- time(qeval(X*ft*lbf/s =:= 1*watt)).
% 2,230 inferences, 0.001 CPU in 0.001 seconds (94% CPU, 4389764 Lips)
X = 2500000000000000r3389544870828501.

The first qeval takes over 20 seconds and the second takes 1 ms. I’ll assume this is due to tabling. This is confirmed if you abolish_all_tables and repeat the experiment. Note that a similar qeval is much faster:

?- abolish_all_tables.
true.

?- time(qeval(X*ft*lbf/s =:= 1*joule/s)).
% 3,674,782 inferences, 0.588 CPU in 0.616 seconds (96% CPU, 6244829 Lips)
X = 2500000000000000r3389544870828501.

Tabling is not free (as the first example shows) so have you done many real comparisons between a tabled and non-tabled version? I’m also a bit concerned at memory consumption for tables that take 20 sec. to build. (And if you think code is at all “buggy” wouldn’t it be much better to work with a non-tabled version?)

Both are fully equivalent. I suppose a better example would be to get the unit of a generic expression:

expression_unit(Expr, Unit) :-
    qeval(Expr =:= _*unit(Unit)).

Of course, you could argue that such functionality should be provided by the library.
I’ll try to add these kind of helper soon (but I want to do documentation before).


So, as I have already said (but not documented :frowning: ), the library parses variables as a unitless value by default, e.g. keeps the same meaning of clpBNR.
The only exception is using X is ... where here X is interpreted as a quantity (the triple Value*Quantity[Unit]) if it is a variable. If the LHS is not a variable, I fall back to the same meaning as =:=`. The reason I did this was because:

  • I thought it would be more convenient to have a syntax that do X is ... instead of quantitiy X is ...
  • however, this introduces a kind of dissymmetry, which is not present in clpBNR.

So, again, this is because of the ambiguous meaning of a variable.
For now, I decided to use different operator is or =:= to disambiguate the meaning.
I’m not sure that I should do such “inference” on the meaning because I think it could weaken the safety of the library, which can be used to do very interesting things with unitless quantities.


Thank you so much for adding test cases.
The conversion code can definitely be improved.
For now, it uses simple BFS with an iterative deepening, the results of which is memoized through tabling.
If you find other edge cases, let me know !

Perhaps I’m just getting more confused and not really helping, but in a language of “quantities” why shouldn’t “naked” variables be treated as unknown quantities (not values). The only other place variables would be allowed is in the value part of the literal quantity (Value*Unit). In such literals, the unit expression must be ground (unless you want to add constraints on undefined units). If this were the case then I think is and =:= could be equivalent.

So what do I lose? Maybe the ability to evaluate expressions like Speed =:= Distance/Time as purely an arithmetic constraint:

?- qeval(Speed =:= Distance/Time).
Speed::real(-1.0Inf, 1.0Inf),
Distance::real(-1.0Inf, 1.0Inf),
Time::real(-1.0Inf, 1.0Inf).

But when is that useful? And can’t you always just use clpBNR directly, i.e., {Speed =:= Distance/Time}?

As you can see I’m struggling to come up with a mental model that fits all this. Probably best just to wait for the documentation you’re working on so I get a more complete picture.

Well, except that multiplication is commutative, so Unit*Value should be parsed the same as Value*Unit.

To be honest, me too.
Prolog has a very mind bending way of complicating things ^^

Well, except that this isn’t really multiplication for this reason. You’ve just chosen to overload the * operator to express literal quantities. Which is why I suggested a different literal syntax in a previous email.

I suspect the issue is more about trying to take a static C++ approach and map it into a dynamic logic programming framework. That’s difficult without having a clear picture of the abstract semantics, i.e., basic data structures and operations on those structures. I keep trying to go back to a simpler view of just simple quantities (values and unit symbols), no kinds, systems, etc. and build back up from there. Probably not as complete as mp-units but maybe delivers much of the value with a much simpler mental model. Feasibility and usefulness T.B.D.