项目作者: Metaxal

项目描述 :
Units and measurements in Racket
高级语言: Racket
项目地址: git://github.com/Metaxal/measures.git
创建时间: 2013-10-26T09:55:40Z
项目社区:https://github.com/Metaxal/measures

开源协议:GNU Lesser General Public License v3.0

下载


Units and Measurements

Units and measurements in Racket, with conversion facilities between
units.

First some warnings:

  • This collection has not been extensively tested. Use with caution and
    please report any error that you
    find
    .

  • Be cautious with non-linear converters (e.g., °F to K), as
    converting a temperature difference is not the same as converting a
    temperature.

  • Some bindings from racket may be redefined, like second, min and
    drop. You can use rename-in to change these name on require.

1. Quick example

Say you are traveling at 50 miles per hour:

  1. > (define my-speed (m* 50.0 mile (m/ hour)))
  2. > (measure->value my-speed)
  3. '(22.352 m (s -1))

How many kilometers/hour is that?

  1. > (measure->value (convert my-speed '(km (h -1))))
  2. '(80.46719999999999 km (h -1))

How many kilometers do you travel during 5 minutes?

  1. > (measure->value (convert (m* my-speed 5 min) 'km))
  2. '(6.7056000000000004 km)

You are quite late and have only 13 minutes left before your meeting,
and you are 21 miles away. How fast would you need to go to be there in
time?

  1. > (measure->value (convert (m/ (m* 21.0 mi) (m* 13 min)) '(mi (h -1))))
  2. '(96.9230769230769 mi (h -1))

2. Basic definitions

A unit is a symbol and an exponent. A measure is a number and a set
of units.

Basic arithmetic operations (m+ m- m* m/ m^) are defined to
work with measures.

To ease human interaction, measures can be written in an simple Domain
Specific Language (DSL). A DSL measure can then be:

  • a (struct) measure,

  • a number,

  • a DSL unit,

  • a list with a number followed by one or more DSL units.

A DSL unit can be:

  • a (struct) unit,

  • a symbol alone (taking the exponent 1 by default),

  • a list with a symbol and an exponent.

You can use the multiplication operator m* to easily build measures.

  1. > (m* 3)
  2. (measure 3 (set))
  3. > (m* 3 's)
  4. (measure 3 (set (unit 's 1)))
  5. > (m* 3 's '(m -1))
  6. (measure 3 (set (unit 'm -1) (unit 's 1)))

The arithmetic operators automatically convert DSL measures into
measures:

  1. > (m+ 2 3)
  2. (measure 5 (set))
  3. > (m/ 3 '(2 s))
  4. (measure 3/2 (set (unit 's -1)))

Measures can be turned back to human readable values with
measure->value:

  1. > (measure->value (m* '(3 s) 5 '(10 m)))
  2. '(150 m s)
  3. > (measure->value (m* '(3 s) '(5 (s -1))))
  4. 15

Adding or subtracting measures with different units raises an
exn:fail:unit exception:

  1. > (measure->value (m+ '(3 m (h -1)) '(2 m h)))
  2. Error: Measures must have the same units.
  3. Got: #<set: #(struct:unit m 1) #(struct:unit h 1)> and
  4. #<set: #(struct:unit h -1) #(struct:unit m 1)>
  5. > (measure->value (m+ '(3 m (h -1)) '(2 m (h -1))))
  6. '(5 m (h -1))

3. Units and conversions

All units have a short and a long name. The short name is the standard
symbol, and the long name is more descriptive:

  1. > mmHg
  2. (measure 166653/1250 (set (unit 'm -1) (unit 's -2) (unit 'kg 1)))
  3. > millimetre-of-mercury
  4. (measure 166653/1250 (set (unit 'm -1) (unit 's -2) (unit 'kg 1)))

By default, all units are converted to SI units. This allows to perform
dimension reductions when possible.

For example:

  1. > N
  2. (measure 1 (set (unit 'm 1) (unit 's -2) (unit 'kg 1)))
  3. > Pa
  4. (measure 1 (set (unit 'm -1) (unit 's -2) (unit 'kg 1)))
  5. > (m/ (m* 3 N) (m* 2 Pa))
  6. (measure 3/2 (set (unit 'm 2)))
  7. > (m* 3 mi)
  8. (measure 603504/125 (set (unit 'm 1)))
  9. > (m+ (m* 3 mi) (m* 2 m))
  10. (measure 603754/125 (set (unit 'm 1)))

But it is possible to avoid the implicit conversion to SI units by
quoting the short name:

  1. > (m* 3 'mi)
  2. (measure 3 (set (unit 'mi 1)))

(Note that quoting is nicely the same as “prevent reduction” to base
units.) Quoted units can be useful in particular in text files from
which to read measures. They can of course be used together:

  1. > (m+ '(5 mi) (m* 2 '(3 mi)))
  2. (measure 11 (set (unit 'mi 1)))

SI units are actually quoted units:

  1. > (equal? (m* 3 m (m/ 1 s s))
  2. (m* '(3 m (s -2))))
  3. #t

However, now it is not possible to add quantities of different units,
even if they have the same dimension:

  1. > (m+ (m* 3 'mi) (m* 2 'm))
  2. Error: Measures must have the same units.
  3. Got: #<set: #(struct:unit m 1)> and #<set: #(struct:unit mi
  4. 1)>

Known quoted units can still be converted back to SI units:

  1. > (convert (m* 3 'mi))
  2. (measure 603504/125 (set (unit 'm 1)))

Using the convert function it is also possible to request a conversion
from SI units to non-SI units (or, more precisely, non-SI-base units):

  1. > (convert (m* 3 m)
  2. 'mile)
  3. (measure 125/67056 (set (unit 'mi 1)))
  4. > (convert (m* 3 ft (m/ s))
  5. '(mi (h -1)))
  6. (measure 45/22 (set (unit 'h -1) (unit 'mi 1)))
  7. > (convert (m* 10 hecto Pa) 'mmHg)
  8. (measure 1250000/166653 (set (unit 'mmHg 1)))
  9. > (m* 2 Pa 3 m m)
  10. (measure 6 (set (unit 'm 1) (unit 's -2) (unit 'kg 1)))
  11. > (convert (m* 2 Pa 3 m m) 'N)
  12. (measure 6 (set (unit 'N 1)))

It can also be used to convert to unit prefixes:

  1. > (measure->value (convert (m* 3 kilo Pa) '(hecto Pa)))
  2. '(30 Pa h.)

Notes:

  • Prefixes are followed by a dot to avoid name collision with units.

  • The order of “units” is first by exponent then alphabetical (ASCII),
    this is why the h. is after Pa.

The convert function accepts a measure and either:

  • the 'base symbol (default), to convert to base (SI by default)
    units,

  • a DSL unit,

  • a list of symbols and DSL units.

It can then be used to convert quoted units to SI units and back to
quoted units. For example, this is not what we want (although it is
correct):

  1. > (convert (m* 3 'mi) 'yd)
  2. (measure 1250/381 (set (unit 'm -1) (unit 'yd 1) (unit 'mi 1)))

This is what we want:

  1. > (convert (m* 3 'mi) '(base yd))
  2. (measure 5280 (set (unit 'yd 1)))

But of course, without quoted units, we could have written:

  1. > (convert (m* 3 mi) 'yd)
  2. (measure 5280 (set (unit 'yd 1)))

4. Dimensions and contracts

Units and measures are organized in dimensions.

For example:

  1. (define-dimension time (s second)
  2. ....
  3. (d day 86400)
  4. (min minute 60)
  5. (y year (m* 1425/4 day)))

This defines a time dimension, a base unit s with a long name
second, and several derived units, where a single number expresses a
ratio with respect to the base unit, and an expression denotes a value
to be used in place of a ratio.

This also defines the time/c contract that can be used in function
contracts:

  1. > (define/contract (speed a-distance a-time)
  2. (length/c time/c . -> . velocity/c)
  3. (m/ a-distance a-time))
  4. > (speed (m* 5 mile) (m* 2 hour))
  5. (measure 1397/1250 (set (unit 's -1) (unit 'm 1)))
  6. > (speed (m* 5 mile) (m* 2 metre))
  7. speed: contract violation
  8. expected: time/c
  9. given: (measure 2 (set (unit 'm 1)))
  10. in: the 2nd argument of
  11. (-> length/c time/c velocity/c)
  12. contract from: (function speed)
  13. blaming: top-level
  14. (assuming the contract is correct)
  15. at: eval:37.0

5. A ’measures’ language

The measures/lang language can be used as a short-hand to have all of
racket plus all of of measures except that the measures arithmetic
operators (m+, etc.) replace the normal ones (+, etc.).

As a consequence, one can write:

  1. #lang s-exp measures/lang
  2. (+ (* 5 mi) (* 5 km))

This is also useful to be used in a terminal by invoking:

  1. racket -li measures/lang

This opens an interaction session where measures/lang is loaded.

6. Chemical elements

The measures/chemical-elements provides the vector elements of the
118 elements with a number of procedures to extract their information:
atomic-number atomic-symbol chemical-element group period
atomic-weight density melting-point boiling-point
heat-capacity electronegativity abundance.

Each procedure accepts either a number (the atomic number) or a symbol
(either the atomic symbol or the name of the chemical element).

Examples:

  1. > (require measures/chemical-elements)
  2. > (atomic-number 'Oxygen)
  3. 8
  4. > (atomic-symbol 'Iron)
  5. 'Fe
  6. > (atomic-symbol 2)
  7. 'He
  8. > (chemical-element 'Na)
  9. 'Sodium
  10. > (atomic-weight 'Carbon)
  11. (measure 1.99447483422e-26 (set (unit 'kg 1)))
  12. > (m* 3 cl (density 'Mercury))
  13. (measure 0.40600800000000004 (set (unit 'kg 1)))

Some useful
conversions
can be
found on Wikipedia (to be trusted with caution of course).

This collection was partly inspired by the Frink programming
language
and Konrad Hinsen’s Clojure
units library
.

See also AlexKnauth’s
measures-with-dimensions.

You may also be interested in Doug Williams scientific
collection
.

8. License and Disclaimer

Copyright (c) 2013 Laurent Orseau

Licensed under the GNU LGPL. See LICENSE.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
“AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.