Did you know ... | Search Documentation: |
![]() | Packs (add-ons) for SWI-Prolog |
Title: | unit and quantity arithmetic for swi-prolog |
---|---|
Rating: | Not rated. Create the first rating! |
Latest version: | 0.18.0 |
SHA1 sum: | 7bb28aa84d8ce382d5a014101c62e4f956d496d4 |
Author: | Kwon-Young Choi <kwon-young.choi@hotmail.fr> |
Maintainer: | Kwon-Young Choi <kwon-young.choi@hotmail.fr> |
Packager: | Kwon-Young Choi <kwon-young.choi@hotmail.fr> |
Home page: | https://github.com/kwon-young/units |
Download URL: | https://github.com/kwon-young/units/releases/*.zip |
Requires: | clpBNR |
prolog>=9.3.23 | |
Provides: | units |
No reviews. Create the first review!.
Version | SHA1 | #Downloads | URL |
---|---|---|---|
0.18.0 | 7bb28aa84d8ce382d5a014101c62e4f956d496d4 | 2 | https://github.com/kwon-young/units |
0.17.0 | 7253a3ce9d556b60b1f417646897ca22c2fc321e | 3 | https://github.com/kwon-young/units |
0.16.0 | d165726d7db9f7bec3e15c001d4888ec0284117e | 5 | https://github.com/kwon-young/units |
0.15.0 | 822b76349c44ee465a88a36e9cadd5830e6d6f81 | 3 | https://github.com/kwon-young/units |
0.14 | 3fea9e532c6d2671b03cf6594535454b474dd9d7 | 4 | https://github.com/kwon-young/units |
0.13 | 0adf3286fe8db3e10ff0454a6cd2f8786dfceda9 | 2 | https://github.com/kwon-young/units |
0.12 | 2cbda8d5c33e1cb20afb2ba05257bfd3157c9f15 | 4 | https://github.com/kwon-young/units |
0.11 | ba518156e3c950a4051cc98f2dc254f9d2dd9715 | 2 | https://github.com/kwon-young/units |
0.10 | ce6c542b76432066937cfb9ff53abc8a0fe5391f | 2 | https://github.com/kwon-young/units |
0.9 | 7026a14ef362774091df7a88d1281da8d908ed08 | 4 | https://github.com/kwon-young/units |
0.8 | 1efb7e08e4c3144b94c3b19a91f0eccd618de9df | 1 | https://github.com/kwon-young/units |
0.7 | 2f51ea3f43008573412c921e04ef1f93b7d48807 | 3 | https://github.com/kwon-young/units |
0.6 | 7c1ef89fd3dba266c749d29534de3bdcdf161648 | 2 | https://github.com/kwon-young/units |
0.5 | e6f43e25124d4a4ae1cadc332062267be0d93406 | 2 | https://github.com/kwon-young/units |
0.4 | 006ebf96f542bcd26605dc33b0b92b2a4af5f02a | 5 | https://github.com/kwon-young/units |
0.3 | 6205801eb7c3db36af683b73af6b3bcf96e89bd5 | 4 | https://github.com/kwon-young/units |
0.1 | 8fbf439a214a72b82623dfdc5d69782318c83860 | 1 | https://github.com/kwon-young/units |
Units is a quantity and units library modeled after mp-units
.
The key features are:
You can install this pack with:
$ swipl pack install units
This library depends on:
To use this library, just wrap all your arithmetic in the qeval/1 predicate.
Use multiplication *
to create quantities:
?- use_module(library(units)). ?- qeval(X is 3*metre). X = 3*kind_of(isq:length)[si:metre].
A quantity is a concrete amount of a unit. It is modeled in the library with the following term: `Amount * QuantityType[Unit]`.
pi
. Anything that can be used with regular prolog arithmetic.System:UnitName
, e.g. si:metre
or si:second
.
This normalized form is composed of two parts:
si
or international
1
denotes that the amount is unitless.System:QuantityName
, e.g. isq:speed
or isq:length
.
The quantity name 1
denotes that the amount is dimensionless.
For example, a speed of `3 metre/second` would be represented as `3 * isq:speed[si:metre/si:second]`.
:warning: `3*isq:speed[si:metre/si:second]` is a quantity and isq:speed
is a quantity type.
To create a quantity, you can multiply a number with a predefined unit:
?- use_module(library(units)). ?- qeval(Q is 3 * si:metre / si:second). Q = 3*kind_of(isq:length/isq:time)[si:metre/si:second].
As you can see, Q is a quantity of amount 3, unit si:metre/si:second
and quantity type kind_of(isq:length/isq:time)
.
This quantity type was derived from the units used in the expression:
si:metre
can be any kind of lengthsi:second
can be any kind of time
For convenience, the same units can be used without their system (the `si:` prefix) or with their symbol (m
for metre and s
for second):
?- qeval(Q is 3 * m / s). Q = 3*kind_of(isq:length/isq:time)[si:metre/si:second].
:warning: The same symbol can be used for multiple units in the library. There are currently no mechanism to avoid name collision, so be extra careful when using them. Known pitfall symbols:ft
usually means foot, but can also meansi:femto(si:tonne)
Quantities of the same kind can be added, subtracted and compared:
?- qeval(1 * km + 50 * m =:= 1050 * m). true.
Quantities of various kind can be multiplied or divided:
?- qeval(140 * km / (2 * hour) =:= 70 * km/hour). true
Here is a quick preview of what is possible:
:- use_module(library(units)). % use multiplication to associate ?- qeval(10*km =:= 2*5*km). % conversions to common units ?- qeval(1 * h =:= 3600 * s). ?- qeval(1 * km + 1 * m =:= 1001 * m). % derived quantities ?- qeval(1 * km / (1 * s) =:= 1000 * m / s). ?- qeval(2 * km / h * (2 * h) =:= 4 * km). ?- qeval(2 * km / (2 * km / h) =:= 1 * h). ?- qeval(2 * m * (3 * m) =:= 6 * m**2). ?- qeval(10 * km / (5 * km) =:= 2). ?- qeval(1000 / (1 * s) =:= 1 * kHz). % assignment and comparison ?- qeval(A is 10*m), qeval(A < 20*km). A = 10 * kind(isq:length)[si:metre].
If you have already used units in a scientific context before, you will probably have heard of dimensional analysis. The goal of dimensional analysis is to check the correctness of your expression. The process is to reduce units to their base quantities (or dimensions):
3 * metre/second
has the dimensions `Length1 * Time-1`
?- qeval(X*Q[U] is 3*m/s), units:quantity_dimensions(Q, D). X = 3, Q = kind_of(isq:length/isq:time), U = si:metre/si:second, D = isq:dim_length/isq:dim_time.
Once this is done, you can check the correctness of your expression. For example, addition, subtraction or comparison should be done on expression with the exact same dimensions:
1 * metre/second + 1 * inch/hour
1 * metre/second + 1 * metre * second
?- qeval(X is 1*metre/second + 1*inch/hour). X = 1.0000070555555556*kind_of(isq:length/isq:time)[si:metre/si:second]. ?- qeval(X is 1*metre/second + 1*metre*second). ERROR: Domain error: `kind_of(isq:length/isq:time)' expected, found `kind_of(isq:length*isq:time)'
While this is a very good system to check the correctness of your expression, it is a bit simplistic for various reasons.
Let's say you want to compute the aspect ratio of a rectangle:
rect_ratio(Width, Height, Ratio) :- qeval(Ratio is Width / Height).
Let's try and use this predicate with a rectangle of width 1*metre
and height 2*metre
.
?- Width = 1*metre, Height = 2*metre, rect_ratio(Width, Height, Ratio). Width = 1*metre, Height = 2*metre, Ratio = 0.5*kind_of(1)[1].
But, what if we made a mistake and inverted width with height ?
?- Width = 1*metre, Height = 2*metre, rect_ratio(Height, Width, Ratio). Width = 1*metre, Height = 2*metre, Ratio = 2*kind_of(1)[1].
We get a wrong aspect ratio with no indication that something is wrong.
This is because Width and Height are both a kind of isq:length
here.
Therefore, we can't detect that something is wrong.
By using strong quantities, we can improve the safety of this code:
rect_ratio(Width, Height, Ratio) :- qeval(Ratio is (Width as isq:width) / (Height as isq:height)). ?- Width = 1*isq:width[metre], Height = 2*isq:height[metre], rect_ratio(Width, Height, Ratio). Width = 1*isq:width[metre], Height = 2*isq:height[metre], Ratio = 0.5*(isq:width/isq:height)[1]. ?- Width = 1*isq:width[metre], Height = 2*isq:height[metre], rect_ratio(Height, Width, Ratio). ERROR: Domain error: `isq:height' expected, found `isq:width'
Similar to the International System of Units (SI), there is the International System of Quantities (ISQ) that defines what are the 7 basic quantities, as well as hundreds of other related quantities. These new quantities forms a complex interconnected graph that defines which quantities are compatible and which are not.
Here is a interesting use of the speed
quantity that can be use to generically describe the ratio of any type of length
by any type of time
:
avg_speed(Distance, Time, Speed) :- qeval(S is Distance / Time as isq:speed). ?- avg_speed(220 * si:kilo(metre), 2 * si:hour, Speed), qmust_be(isq:speed[si:kilo(metre)/si:hour], Speed). Speed = 110 * isq:speed[si:kilo(metre)/si:hour]. ?- avg_speed(220 * isq:height[inch], 2 * si:second, Speed). Speed = 110 * isq:speed[international:inch/si:second]. ?- avg_speed(220 * si:gram, 2 * si:second, Speed). ERROR: Domain error: `kind(isq:mass)/kind(isq:time)' expected, found `isq:speed'
The qmust_be/2 predicate can be used to check the quantity and unit of a result.
Some quantities like temperature requires a specific origin from where we measure a displacement.
For example, si:kelvin
measures temperatures from the absolute zero and works like any other units (like metre or second).
Although si:degree_Celsius
are the same as si:kelvin
(1*degree_Celsius =:= 1*kelvin
), they measure temperatures from different origins: si:kelvin
from the absolute zero and si:degree_Celsius
from the ice freezing point.
To model this, the original mp-units
library introduced the notion of quantity points.
In this system, quantities are actually quantity vectors, meaning they represent a quantity change, or a displacement of some sort.
To represent a measurement, we also need to represent origins: the ice freezing point for degree Celsius or the sea level for altitude.
Therefore, A quantity point is the combination of an origin and a quantity vector.
This library model them with the term Origin + Quantity
.
Origins are terms similar to units and can be either:
si:absolute_zero
is not defined in term of something else, it is absolutesi:zeroth_degree_Celsius
is defined as a point relative to si:absolute_zero
, it is relativeqeval(Expr)
: Evaluates the arithmetic expression Expr involving quantities, units, and quantity points. This is the primary predicate for performing calculations with the library. It handles all supported operators and conversions. For example, qeval(X is 3*metre + 50*centimetre)
will bind X to the resulting quantity.qformat(Quantity)
: Formats the given Quantity into a human-readable string using symbols for units.Here are the list of supported arithmetic expressions for quantities:
R is Expr
: If R is a variable, it is interpreted as a quantity `V*Q[U]`A =:= B
: equalityA =\= B
: inequalityA < B
: less thanA =< B
: less than or equalA > B
: greater thanA >= B
: greater than or equal+A
and -A
A + B
, A - B
: A and B should have compatible quantity type and unitsA * B
A / B
A ** N
or A ^ N
: N should be a numberQuantity as QuantityType
: convert the quantity type of Quantity into QuantityType, e.g. metre/second as isq:speed
:
Q` will be interpreted as a quantity, e.g. `V*Q[U]`:
U` will be interpreted as a unit, i.e. metre
is
)point(Expr)
: Interprets Expr as a quantity and creates a quantity point. The origin is inferred from the unit of Expr if defined (e.g. degree_Celsius
), otherwise it defaults to 0
. Example: point(10 * metre)
results in `0 + 10 * kind_of(isq:length)
[si:metre]`. point(20 * degree_Celsius)
results in `si:zeroth_degree_Celsius + 20 * kind_of(isq:thermodynamic_temperature)
[si:degree_Celsius]`.quantity_point(QP)
: Ensures QP is treated as a quantity point, e.g. Origin + Quantity
.origin(O)
: Interprets O as an origin, i.e. si:ice_point
quantity_from_zero(Expr)
: Computes the quantity vector from the default origin 0
to the point Expr. This is equivalent to Expr - origin(0)
.quantity_from(Expr, Origin)
: Computes the quantity vector from a given Origin to the point Expr. This is equivalent to Expr - Origin
.point_for(Expr, NewOrigin)
: Represents the point Expr from the perspective of NewOrigin. For example, if Expr is OriginA + QV_A
, this predicate calculates QV_B such that NewOrigin + QV_B
is the same absolute point as Expr.
This library integrates with SWI-Prolog's library(error)
to allow type checking using must_be/2.
You can use must_be/2 to assert that a term is a specific quantity type.
For example, to ensure a variable Speed is indeed a quantity of type isq:speed
:
?- use_module(library(error)). ?- qeval(Speed is 10 * m/s), must_be(isq:speed, Speed). Speed = 10*kind_of(isq:length/isq:time)[si:metre/si:second].
If the term is not of the expected quantity type, must_be/2 will throw an error:
?- qeval(Dist is 10 * m), must_be(isq:speed, Dist). ERROR: Type error: `isq:speed' expected, found `10*kind_of(isq:length)[si:metre]' (a compound)
You can also check for a generic quantity (any kind) using quantity
:
?- qeval(X is 3*m), must_be(quantity, X). X = 3*kind_of(isq:length)[si:metre].
One peculiar feature is that this library also supports clpBNR arithmetic:
?- qeval({A*metre =:= B*inch}), A = 1. A = 1, B = 5000r127.
You can wrap your expression in {}/1
to explicitly use clpBNR.
The library will also default to clpBNR if the expression is not sufficiently ground to use traditional arithmetic:
% be default, a variable on the left hand side of `is` is % interpreted as a quantity ?- qeval(Speed is L*metre / T*second). Speed = _A*kind_of(isq:length*isq:time)[si:metre*si:second], ::(_A, real(-1.0Inf, 1.0Inf)), ::(L, real(-1.0Inf, 1.0Inf)), ::(T, real(-1.0Inf, 1.0Inf)). % when using equality `=:=`, you need to disambiguate between % a clpBNR variable and a variable that should be bound to a quantity ?- qeval(quantity(Speed) =:= L*metre / T*second). Speed = _A*kind_of(isq:length*isq:time)[si:metre*si:second], ::(_A, real(-1.0Inf, 1.0Inf)), ::(L, real(-1.0Inf, 1.0Inf)), ::(T, real(-1.0Inf, 1.0Inf)).
One can also use a variable for units by wrapping it with unit(Variable)
:
?- qeval(Amount*unit(Unit) =:= 3*metre). Amount = 3, Unit = si:metre.
This means you can write unit aware true relational predicates:
avg_speed(Distance, Time, Speed) :- qeval(Speed =:= Distance / Time as isq:speed). ?- avg_speed(m, s, quantity(Speed)). % traditional forward mode Speed = 1*isq:speed[si:metre/si:second]. ?- avg_speed(quantity(Distance), s, m/s). % "backward" mode Distance = 1*isq:length[si:metre]. ?- avg_speed(X*inch, hour, m/s). % overspecified units X = 18000000r127. ?- avg_speed(quantity(Distance), quantity(Time), quantity(Speed)). % underspecified units Distance = _A*isq:length[_B], Time = _C*isq:time[_D], Speed = _E*isq:speed[_F], ... % lots of constraints
Here is an exhaustive list of quantities and units that you can use out of the box with this library.
You will also find a hierarchical graph representation of quantities in [quantities.pdf](quantities.pdf)
You can extend the library by defining your own custom units and quantities. This is achieved by adding clauses to the library's multifile predicates. The file currency.pl file provides a practical example of how to do this.
Here's a breakdown of the key predicates you'll use:
units:quantity_dimension(QuantityName, Symbol)
to declare a new base quantity and its symbol. For example, to define currency
with symbol '$'
:
units:quantity_dimension(currency, '$').
units:unit_symbol(UnitName, Symbol)
to associate a unit name with its display symbol. For example, for euros and US dollars:
units:unit_symbol(euro, €). units:unit_symbol(us_dollar, usd).
units:unit_kind(UnitName, QuantityName)
to link a unit to its corresponding quantity type. This tells the system what kind of quantity the unit represents.
units:unit_kind(euro, currency). units:unit_kind(us_dollar, currency).
By defining these predicates, your custom units and quantities will be integrated into the system, allowing them to be used with qeval/1 and other library features.
common_expr
(does unification instead of generate and test)common_expr
by partitioning along dimensions.quantities.pdf
) (`9b475d9`, `5637d2c`).eec9baf
).ff92a11
, b4e0f0c
).eval_/2
(e17bbda
).radian
unit test to correctly expect an error (e999fdc
).prolog/units.pl
(c109549
).b4e0f0c
).README.md
with detailed explanations of quantity types, quantity points, qeval/1 usage, clpBNR
support, and how to add custom units/quantities (b49f10e
, `6e36431`, abf82ee
, b0bd0e7
).Quantities.md
) and units (Units.md
) (c97724d
).cfb6a36
).alias_quantity_/1
(`496a997`, `2e9c42d`).Pack contains 50 files holding a total of 1.5M bytes.