Harvey Mudd College

Computer Science 60

Fall 1997

Assignment 2

Due Fri. 3 October 1997

unicalc kernel

This assignment is 50 points of your overall grade.

1. [3 points] (This is just an unrelated warm-up problem.) Construct in rex a function numBits which gives the number of bits in the binary representation of its natural number argument:

test(map(numBits, range(0, 20)),
[1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5]);

Note: You should not have to construct the binary representation explicitly.


The remainder of the problems are stated with respect to the unicalc program, which is described next. Later on the term, we'll devise a Java version of unicalc, based upon what we do here. The program unicalc converts quantities expressed in one type of unit to those of another. Here is an example of unicalc in action on turing. The user types the bold-face characters.


    To convert from units of: miles
    to units of: feet
    Multiply by: 5280
    or
    Divide by: 0.000189394

    To convert from units of: miles / hour
    to units of: feet / second
    Multiply by: 1.46667
    or
    Divide by: 0.681818

    To convert from units of: kg / meter second^2
    to units of: pascal
    Multiply by: 1
    or
    Divide by: 1

Unicalc works on multiplicative conversions only, so it does not perform conversions such as Fahrenheit to Celsius. Unicalc can handle conversions involving multiplication, division, and raising to powers (the ^ symbol above).

The kernel of unicalc is naturally implemented using functional programming. In this assignment, we walk through some of the data representations and issues, and ask you to write the functional code in rex.

To start with, unicalc reads a conversion database, which is essentially a list of equations defining one type of unit in terms of another. For example, mile might be defined in terms of foot, foot in terms of inch, etc. This database does not contain conversions for everything to everything; that would tend to make it very large and error-prone. Instead, all conversions done by unicalc are derived from basic ones in the database.

Not every unit is defined in terms of others in the database. Some are intentionally left undefined. We'll call these basic units.

When unicalc is asked to convert a number of units A into a number of units B, both A and B are converted into expressions involving only basic units, say A' and B' respectively. If A and B are interconvertible, only the numeric multipliers in A' and B' should be different. The result is then simply the ratio of the numeric multipliers for the "multiply by" part of the answer, or its reciprocal, for the "divide by" part.

The internal representation of a quantity in unicalc is a list of three items:

For reasons to be described, the lists are sorted alphabetically. Each of the lists is considered to be a product of the units within. For example, the quantity we would write as 1 kg / meter second^2 would have the following representation:

[1.0, ["kg"], ["meter", "second", "second"]]

 

2. [10 points] The representation of a quantity is said to be reduced if there is no element common to its numerator and denominator lists. A way to convert a quantity to an equivalent reduced form is to simply remove any pairs of elements common to its numerator and denominator. Write a rex definition for a function reduce of one argument (not to be confused with the built-in function reduce in rex which takes three arguments) which yields a corresponding reduced representation. For example:

test(reduce([3, ["kg", "meter", "second", "second"], 
                ["kg", "kg", "second"]], 
 
            [3, ["meter", "second"], ["kg"]]);

(Hint: Use a rule-based approach and exploit the fact that the lists are sorted.)

 

3. [10 points] Construct rex functions multiply and divide which respectively multiply and divide two quantities, yielding a reduced result. For example:

test(multiply([2.0, ["kg"], ["second"]], [3.0, ["meter"], ["second"]]),
 
              [6.0, ["kg", "meter"], ["second", "second"]]));
 

(Hint: Use the previous hint.)

 

4. [5 points] The unicalc database is represented as an association list, a list of pairs consisting of a single unit and a reduced expression for the unit. Essentially these pairs are defining equations for various units, and we can refer to the elements of the pairs as the LHS (left-hand side) and RHS (right-hand side) for this reason. For example, we'd expect to find in the database pairs such as:


  ["foot",   [12.,       ["inch"],   []]]
  ["mile",   [5280.,     ["foot"],   []]]
  ["inch",   [0.0253995, ["meter"],  []]]
  ["hour",   [60.,       ["minute"], []]]
  ["minute", [60.,       ["second"], []]]
  ["mph",    [1.,        ["mile"],   ["hour"]]

(The [ ] above represent the denominators of the RHS. These can be seen as the unit or multiplicative identity for the type of symbolic multiplication we use.)

If this were the entire database, we note that "meter" and "second" are both basic quantities, since they are not the LHS of any definition.

Write a rex function conv_unit which takes a string representing a unit and the database list as arguments. If the unit is defined in the database, it returns the RHS, otherwise it returns a reduced expression representing the unit. For example, if db is the database consisting of the list of the pairs above, then:

test(conv_unit("hour", db), [60.0, ["minute"], [] ] );
 
test(conv_unit("meter", db), [1.0, ["meter"], [] ] );

 

5. [15 points] When the lists in such a representation contain only basic units, we'll say that the representation is normalized. In order to convert one quantity to a multiple of another, we must both normalize and reduce the quantities. Modify your function conv_unit to one called norm_unit which returns a normalized representation of the unit.

Here's how norm_unit would work: In the case that conv_unit finds the unit as an LHS in the database, we want to go through the numerator and denominator lists of the RHS, normalizing the units in the lists and multiplying (using your function multiply) the results together. Since your multiply function always yields a reduced result, you are guaranteed that the product of a list of units is reduced. Once you have normalized both the numerator and denominator, use your divide function to get the overall result of normalization, then combine with the multiplier. This process is recursive, as norm_unit calls itself when it normalizes the units in each of the RHS lists.

test(norm_unit("hour", db), [3600.0, ["second"], [] ] );

 

6. [5 points] Construct the function norm which normalizes any expression with respect to the database. Since an expression always has the form [M, N, D], and N and D consist of lists of units, you would apply norm_unit to each element in N and D, multiply the results together to get normalized quantities N1 and D1, then divide N1 by D1 and combine with the factor M. In fact, you already did something very much like this in part 5, so you may wish to go back and replace that code with a use of norm to make it more elegant. This introduces mutual recursion between norm and norm_unit, but that's ok.

test(norm([1, ["hour"], []], db), [3600.0, ["second"], [] ] );

 

7. [2 points] The conversion process from A to B can now be seen as simply

               divide(norm(A, DB), norm(B, DB))

If direct conversion is possible, the numerator and denominator of this result should both be empty lists. If there is a mismatch, the numerator or denominator will show the deficit multipliers. Provide a user interface function convert such that convert(A, B) will perform the conversion and output the units if a direct conversion is possible, or which explains the deficit if not, in the form shown below. Examples:

    test(convert([1, ["mile"], []], [1, ["inch"], []]),
         "multiply by 63360 or divide by 1.57828e-05");

    test(convert([1, ["mile"], ["second"]], [1, [], []]),
         "There is a mismatch in units."); 
For converting numbers to strings, you may wish to use the rex built-ins make_string which makes a string of its argument, and concat which concatenates an arbitrary number of string arguments.

In /cs/cs60/rex you will find:

unicalc_mini_db.rex

an equation for db defining the mini database above

unicalc_db.rex

an equation defining a more comprehensive database

Test cases will be found in:

 
     /cs/cs60/a/a2.rex