Hello everybody,
Here is a new version of the units pack (v0.12) with these new features:
New features
- 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
- automatic use of clpBNR for arithmetic if expression is not sufficiently ground
- pure quantities and units handling with constraints !
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.
Beware that this also increase the likelyhood of symbol collision ↩︎