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.21.0 |
SHA1 sum: | e46b704bbf1e33a7ae7a062063865b4e938eb0ee |
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.21.0 | e46b704bbf1e33a7ae7a062063865b4e938eb0ee | 3 | https://github.com/kwon-young/units |
0.20.0 | 98e950c14f984f1e23cc47388019772889d2163a | 4 | https://github.com/kwon-young/units |
0.19.0 | 80809d88e74e64f9f0a8dff4d6827565edb0222d | 2 | https://github.com/kwon-young/units |
540d398afee18cf42dfde1eb6886f63f33761095 | 1 | https://github.com/kwon-young/units | |
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)). % imports qeval/1 ?- use_module(library(units/systems/si)). % loads the si system of units: si:metre... ?- qeval(X is 3*si: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)). ?- use_module(library(units/systems/si)). ?- 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 / second). Q = 3*kind_of(isq:length/isq:time)[si:metre/si:second].
By default, importing a system of units (use_module(library(units/systems/si))
) will enable the use of symbols for all units defined in this system.
This will introduce a predicate per symbol and unit name in the current context module.
These predicates will be used to disambiguate the use of symbols, as well as make sure that no duplicate symbols can be used in the same context.
For example, importing symbols for both si
and usc
systems will result in a number of predicate name collisions:
?- use_module(library(units/systems/si)). ?- use_module(library(units/systems/usc)). ERROR: import/1: No permission to import usc_symbol:(min/1) into user (already imported from si_symbol) ERROR: import/1: No permission to import usc_symbol:(t/1) into user (already imported from si_symbol) ERROR: import/1: No permission to import usc_symbol:(c/1) into user (already imported from si_symbol) true.
You can resolve conflicts by using the use_module/2 directive by including, excluding or renaming predicates:
?- use_module(library(units/systems/si), except([t/1])). ?- use_module(library(units/systems/usc), [t/1, min/1 as myminim]). true. ?- qeval(X is t), qeval(Y is myminim). X = 1*kind_of(isq:mass)[usc:ton], Y = 1*kind_of(isq:length**3)[usc:minim].
:warning: Be aware that importing a symbol module will introduce a lot of short named predicates. This can potentially cause naming collision with your own code. Therefore, when using symbols in code, try to import only the symbol used and no more. Even better is to avoid symbols in code altogether, and only used symbols for oneoff interactive queries on the top level.
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.
Be aware that the symbols referenced in Units.md
are symbols as they would be printed by qformat
and not necessarily how you would use them in code.
You will also find a hierarchical graph representation of quantities in [quantities.pdf](quantities.pdf)
Here is a list of available systems:
units/systems
to load all systems.
In that case, no symbols will be exported.You can use the qformat/1 to pretty print your quantities with unit symbols:
?- qformat(1r3*si:micro(si:ohm)). 1r3 µΩ
There is a two arguments version to add a format string for the value, the default above being "~p":
?- qformat("~f", si:micro(si:ohm)). 0.333333 µΩ
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).
qeval
, you need to provide 1 arity predicate of the form `Symbol(Unit)` that can be imported in the user module:
:- module(currency_symbols, ['€'/1, usd/1]). '€'(euro). usd(us_dollar).
The symbol name is arbitrary, but be very careful that every predicate is unique.
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 63 files holding a total of 1.6M bytes.