OASIS Mailing List ArchivesView the OASIS mailing list archive below
or browse/search using MarkMail.

Re: An approach to describing the relationships betweenunits-of-measure

[ Lists Home | Date Index | Thread Index ]

```
costello@mitre.org@INTERNET@wtgw'(Roger L. Costello):
| EXAMPLE 1
|
| To explain the approach I will use this example:
|
|    <River id="Yangtze">
|         <length>
|             <kilometer>6340</kilometer>
|         </length>
|    </River>
|
| Recall that <kilometer> may be considered as a function:
...
|     kilometer(Distance) --> number

I'd say that more usually kilometre is considered a function that maps a number to
a distance. If you consider the canonical distance to be a metre, you can define:
(defun kilometre (x) (uom-* 1000 (metre x)))
; where
(defun uom-* (scalar uom) (list (car uom) (* scalar (cadr uom))))
(defun metre (x) (distance x))
(defun distance (x) (list 'distance x))

I don't do MathML, so you'll have to make do with lisp as it's a convenient
functional language.

I prefer that the kilometre function adds information to a number making it a

<River id="Yangtze">
<length>
<kilometer>6340</kilometer>
</length>
</River>
-->
(defclass River () ((id :initarg :id :accessor :id) (length :initarg :length :
accessor :length)))
(defmacro River (&rest r) (apply 'make-instance 'River r))

(River :id Yangtze :length (kilometre 6340))
=> #<RIVER>

| Likewise, for this version:
|
|    <River id="Yangtze">
|         <length>
|             <mile>3914</mile>
|         </length>
|    </River>
|
| mile is a function that maps a Distance to a number:
|
|     mile(length(Yangtze)) --> 3914
|
| or more generally:
|
|     mile(Distance) --> number

alternatively
(defun mile (x) (uom-* 5280 (foot x)))
(defun foot (x) (uom-* 12 (inch x)))
(defun inch (x) (uom-* 0.0254 (metre x)))

; or in prefix notation:
;    mile(number) => 5286 * foot (number) = 63360 * inch (number)
;                 = 1609.35 * metre (number)

so
<River id="Yangtze">
<length>
<mile>3914</mile>
</length>
</River>
-->
(River :id Yangtze :length (mile 3914))
=> #<RIVER>

| "Those instances that represent equivalent Distances are
| the set of k (kilometer), m (mile) pairs such that k = m * 1.62"

Are the length properties of two rivers the same?

(setf yang-0 (River :id Yangtze :length (kilometre 6299)))
(setf yang-1 (River :id Yangtze :length (mile 3914)))

(:length yang-0) => (kilometre 6299)
(eval (:length yang-0)) => (DISTANCE 6299000)

; equivalent-uom-value tests the evaluated form of two lists
; and compares the numeric values in the second member each the
; list, using the second member of the tolerance list as a absolute
; tolerance if present. If the dimensions of the values are not
; equal it will return false.
(defun equivalent-uom-value (x y &optional &key (tolerance nil))
(let ((x (eval x)) (y (eval y)) (tolerance (eval tolerance)))
tolerance)))
(equal (car x) (car y))
(equal (car x) (car tolerance)))
(equal x y))))
=> EQUIVALENT-UOM-VALUE

(equivalent-uom-value (:length yang-0) (:length yang-1))
=> NIL

(equivalent-uom-value (:length yang-0) (:length yang-1) :tolerance (metre 30))
=>T

(defun degree (x) (uom-* (/ pi 180) (radian x)))
(defun angle (x) (list 'angle x))

(equivalent-uom-value (metre 7) (radian 7)) => NIL

Implicitly an equivalence relation defines a set.

| For example, (6340 k, 3914 m) represent equivalent Distances.

BTW
(equivalent-uom-value (mile 3914) (kilometre 6340) :tolerance (kilometre 40)) =>
NIL
wheres
(equivalent-uom-value (mile 3914) (kilometre 6299) :tolerance (foot 100)) => T

So I guess that either kilometres are subject to an import duty on entering the USA,
or your mile isn't a statue mile ;).

| I envision a document containing class definitions (e.g.,
| the Distance class). I envision an API to enable programs to
| programmatically access the class info via the API.

I've done similar to the above, but with functions that convert numbers to
distances rather than distances to numbers in a several hundred lines of Java, with
XML data files like:
<Blah
length = "2.134"
length_units = "km"
/>

The syntax of which is mainly due to XMI->Java mapping limitations.

| This API may have a method such as this:
|
|      boolean isContainedIn((uom1_value, uom2_value), set);
|
| The method, isContainedIn, has two arguments. The first argument
| contains a pair of unit-of-measure values, and the second argument
| is a set definition.  The result is a boolean.

What would be the truth table of this method? If the set is the set of equivalent
uom_values, then it's infinite and exposing it as the api has no more functionality
than exposing the function equivalent-uom-value.

| <Class id="Distance">
|     <equivalentInstances>
|         {k, m | k = m * 1.62}expressed as MathML
|         {k, m | k = m * 1.62}expressed as Mathematica
|         {k, m | k = m * 1.62}expressed as xPath(???)
|     </equivalentInstances>
| </Class>

Do you really want to define units^2*representations functions, or wouldn't it be
better to define one function for each units (in whatever representation) that
generates a canonical form for the equivalence realtion?

| Do you have suggestions on how to represent tolerance?

- fixed, as a uom-value, eg  the example I gave above
- relative to one of the input values

| EXAMPLE 2
...
| Here is the completed Location class:
|
| <Class id="Location>
|     <equivalentInstances>
|         {c, p | c.x.kilometer = p.r.kilometer sin p.theta.radian ^
|                      c.y.kilometer = p.r.kilometer cos p.theta.radian}
|     </equivalentInstances>
| </Class>
|
| Note: I am not convinced that this is the best way to express the set.
| Do you have any suggestions?

I'd call it coordianate-2d or something; a general location needs 3D [+ time stamp]
[+ tolerance/confidence], a co-ordinate system and possibly a reference point (eg:
lat/long/alititude the reference point is implied but inertial systems
distance-north/distance-east/distance-down the reference point is also required)

Unfortunately, there isn't a single canonical form for equivalence:
if p0 is a cartesian point with a tolerance t0 of +/- x metres in any direction
and p1 a polar point with a tolerance t1 of +/- y degrees and +/- z range
(equivalent-location (to-polar p0) p1 t1)
<=/=>
(equivalent-location p0 (to-cartesian p1) t0)
though it should be the case, subject to rounding errors, that
(equivalent-uom-value (distance-between (to-polar p0) p1) (metre 0) :tolerance
(metre some-tolerance))
<==>
(equivalent-uom-value (distance-between p0 (to-cartesian p1)) (metre 0) :
tolerance (metre some-tolerance))

| API  REVISITED
|
| Recall the API that was mentioned above:
|
|     boolean isContainedIn((uom1_value, uom2_value), set, tolerance);
|
| Recall that "tolerance" is used to indicate how much slack you
| are willing to accept in deciding if a pair of values are contained
| in the set.  In this Map example the tolerance is effectively a
| way of indicating "I want all values within this region".  When
| you tighten the tolerance, you narrow the region.  When you loosen
| the tolerance, you widen the region. I think this will be very useful.

In this case tolerance then would consist of a function to covert uom1_value and
uom2_value to a common form, a function to compute the difference in some form, and
a value that the difference value have to be less than.

For the length case, the common form is the canonical form SI metres; for the
location case different tolerances (cartesian-tolerance, polar-tolerance,
distance-tolerance) would require different common forms and difference
computations.
eg:
(equivalent-location p0 p1 (polar-tolerance :theta (degrees 10) :r (kilometres
50)))
would test if p0 and p1 have bearings within ten degrees of each other and ranges
within 50km of each other from an implied origin
(equivalent-location p0 p1 (distance-tolerance (foot 10)))
would test if p0 and p1 are within 10 feet of each other

As a quick-hacK:
; super class for 2d coordianate. serves no real purpose
(defclass coordianate-2d () ())
; cartesian location and constructor method
(defclass cartesian-coordinate (coordianate-2d) ((x :initarg :x) (y :initarg :y)))
(defun cartesian-coordinate (&key x y) (make-instance 'cartesian-coordinate :x x :y
y))

; polar location and constructor method
(defclass polar-coordinate (coordianate-2d) ((r :initarg :r) (theta :initarg :
theta)))
(defun polar-coordinate (&key theta r) (make-instance 'polar-coordinate :theta
theta :r r))

;<Map id="M21">
;    <location>
;        <polar-coordinate>
;            <r>
;                <kilometer>141.421</kilometer>
;            </r>
;            <theta>
;            </theta>
;        </polar-coordinate>
;    </location>
;</Map>

(defclass Map () ((location :initarg :location :accessor :location)))
(defmacro Map- (&rest r) (apply 'make-instance 'Map r))

(setf m (Map- :location (polar-coordinate :r (kilometer 141.421) :theta (radian 0.
7841)))) => #<Map?>

(:location m) => (POLAR-COORDINATE :R (KILOMETER 141.421) :THETA (RADIAN 0.7841))

(defmethod to-cartesian ((pt cartesian-coordinate)) pt)
(defmethod to-cartesian ((pt polar-coordinate))
(let
( (r (eval (:r pt)))
(theta (eval (:theta pt))))
(if (and (eql 'distance (car r)) (eql 'angle (car theta)))
(make-instance 'cartesian-coordinate

(defmethod to-polar ((pt cartesian-coordinate))
(let
( (x (eval (:x pt)))
(y (eval (:y pt))))
(if (and (eql 'distance (car x)) (eql 'distance (car y)))
(make-instance 'polar-coordinate
(defmethod to-polar ((x polar-coordinate)) x)

; haven't bothered with checking type of x and y
(defun distance-between (p0 p1) (let ((p0 (to-cartesian p0)) (p1 (to-cartesian
p1)))
(let
(distance (sqrt (+ (* dx dx) (* dy dy)))))))

; create a comparison with based on an absolute tolerance
(defun distance-tolerance (tolerance)
(lambda (p0 p1)
(equivalent-uom-value (distance-between p0 p1) (distance 0) :tolerance
tolerance)))

; do something similar for polar-tolerance- creates an appropriate comparitor
function
; by converting p0 and p1 to polar and taking the difference
; (defun polar-tolerance (&key r-tolerance theta-tolerance) ...

; the equivalence function for locations
(defun equivalent-location (p1 p2 &optional &key (tolerance (distance-tolerance
(metre 1))))
(funcall tolerance p1 p2))
; default tolerance is 1 metre

(setf *WARN-ON-FLOATING-POINT-CONTAGION* nil)
; it's quite contagious

(setf p0 (polar-coordinate :theta '(degree 90) :r '(metre 1609)))
(setf p1 (cartesian-coordinate :x (mile 0) :y (mile 1)))

(equivalent-location p0 p1) => T
(equivalent-location p0 p1 :tolerance (distance-tolerance (metre 10)))  => T
(equivalent-location p0 p1 :tolerance (distance-tolerance (metre 0.01)))  => NIL

(setf p2 (cartesian-coordinate :x (mile 20) :y (mile 0)))

(equivalent-location p0 p2) => NIL
(equivalent-location p0 p2 :tolerance (distance-tolerance (kilometre 40))) => T

; but comparing two values with tolerances isn't a strict equivalence relation
; as it's not always transitive
(setf p0 (cartesian-coordinate :x (metre 1.1) :y (metre 0)))
(setf p1 (cartesian-coordinate :x (metre 2) :y (metre 0)))
(setf p2 (cartesian-coordinate :x (metre 2.9) :y (metre 0)))

(equivalent-location p0 p1) => T
(equivalent-location p1 p2) => T
(equivalent-location p0 p2) => NIL

I'd guess there's similar constructs in the languages you're using.

Pete

********************************************************************
This email and any attachments are confidential to the intended
recipient and may also be privileged. If you are not the intended