<
Warning: This page usesjsMathto render equations, and it requires JavaScript to process the mathematics on this page.
If your browser supports JavaScript, be sure it is enabled. Otherwise you'll just see LaTeX style markup.

Assignment 4

Harvey Mudd College
Computer Science 42
Assignment 7, Due Monday, March. 26, by 11:59pm

Unicalc Part 1

Unicalc is a calculator program that handles numerical quantities including physical and other units, e.g.,


420 seconds
4.20 meters / second
42.42 mile / hour + 99 km / day

Units are important to computation because they eliminate an element sometimes left to human interpretation. In the area of engineering, failure to interpret numbers without their units specified has been known to lead to failure of space missions. In the Wikipedia article about NASA's Mars Climate Orbiter, it is stated:

“Twenty-four hours prior to orbital insertion, calculations placed the orbiter at an altitude of 110 kilometers; 80 kilometers is the minimum altitude that Mars Climate Orbiter was thought to be capable of surviving during this maneuver. Final calculations placed the spacecraft in a trajectory that would have taken the orbiter within 57 kilometers of the surface where the spacecraft likely disintegrated because of atmospheric stresses. The primary cause of this discrepancy was human error. Specifically, the flight system software on the Mars Climate Orbiter was written to calculate thruster performance using the metric unit Newtons (N), while the ground crew was entering course correction and thruster data using the Imperial measure Pound-force (lbf).”

A little closer to home, you will have to do many unit conversions and operations in your other classes here at HMC. It is our hope that this calculator will prove useful in those classes.

This week you will begin by implementing the back-end functionality for simple unit calculations with error propagation. In doing so, you will be implementing an API (Application Programming Interface), which is simply a library of visible functions that others can use. In this case, you will be using those functions next week when implementing the Unicalc calculator.

Error Propagation

One of the important things you will learn in some of your science classes (if you haven't already) is that real calculations are rarely exact. For example, consider the problem of calculating the average velocity of a unicyclist as she races across campus. One way you might do this is to measure the distance between West Dorm and Hoch Shanahan, and you might even put tape marks on the ground. Say this distance is 22.15m. Now, as soon as you see her cross the first tape mark, you start your watch. When you see her cross the second mark, you stop your watch. 9.95 seconds it says. How fast was she going? Simple, right? v = x / t = 22.1m/9.95s = 2.32 m / s. A perfect problem for Unicalc!

But wait, not so fast... your friend was also timing her, and he says that she took 10.32 seconds. Furthermore, your other friend re-measured the distance between the tape marks, and she says that the distance is actually 22.07m. Hmmm, things are getting a little more complicated...

As you may have already learned, whenever you take a measurement, there is some room for error in that measurement. As scientists, we quantify this error, so that we can reason rationally about it, rather than simply guessing about how it might affect our calculations.

In the above problem, each measurement has its own error. When you put them together to calculate the velocity, you need to somehow account for, or propagate, the error through the equation. Fortunately there is a pricipled way to do this, and this is what you will build into Unicalc this week.

The details of error propagation are beyond the scope of this class. You'll learn about the theory behind error propagation in Physics and Chemistry. (Though if you can't wait, you can take a look at this site.) For now, all you need to know are the equations for error propagation for standard arithmetic operations.

Let's assume that you have two quantities, x and y, with a standard (i.e. normally distributed, random) error \(E_x\) and \(E_y\). For example, instead of knowing that \(x\) is exactly 22.1m, you estimate that \(x\) is 22.1m plus or minus 0.05m. That is, \(x\)=22.1m and \(E_x\)=0.05m. Again, how exactly you would calculate this standard error we won't deal with here. We'll assume the errors are given.

Now you wish to find the standard error of the result of performing some artimetic operation on \(x\) and \(y\). Call the result \(z\), and the resulting error \(E_z\).

Here are the rules for error propagation, depending on how you use \(x\) and \(y\) to get \(z\)

\[ \displaystyle \begin{array}{ll} \displaystyle z = x + y & E_z = \sqrt{E_x^2 + E_y^2}\\ \ \\ z = x - y & E_z = \sqrt{E_x^2 + E_y^2}\\ \ \\ z = xy & \frac{E_z}{|z|} = \sqrt{\left(\frac{E_x}{x}\right)^2 + \left(\frac{E_y}{y}\right)^2 }\\ \ \\ z = \frac{x}{y} & \frac{E_z}{|z|} = \sqrt{ \left(\frac{E_x}{x}\right)^2 + \left(\frac{E_y}{y}\right)^2 }\\ \ \\ z = x^m & \frac{E_z}{|z|} = |m|\frac{E_x}{|x|}\\ \end{array} \]

Error propagates the same way for addition and subtraction, and the same way for multiplication and division. Note that the error associated with \(x^2\) CANNOT be calculated by applying the multiplication rule to \(x\times x\). This is because the multiplication rule assumes that the two quantities (and their errors) are independent. This is obviously not the case for \(x\) combined with itself.

When implementing these formulas in Java, you will likely want to use the functions Math.sqrt(...) and Math.abs(...) and Math.pow(..., ...). Here is the documentation for the Math class.

Part 1: Quantity.java Basics (75 points) — Individual or Pair

An object of class Quantity should represent a numerical value having numerical value and uncertainty and attached units, e.g., “9.8 +/- 0.1 meters per second squared.” A Quantity object always has these three pieces of data, although the uncertainty might be 0 (if we know the number exactly) and the collection of units might be empty (for “dimensionless” numbers like π).

Start out by creating a file Quantity.java that contains a definition of a class Quantity. The Quantity class must contain at least the following fields:

To start with, create and test the following public methods in the Quantity class: You should feel free to add additional constructors and additional (private) helper methods as you see fit. For example, I wrote a method adjustExponentBy(String unitName, int delta) that adds delta to the existing exponent in the units map. (It also adds the unit to the map, if it wasn't there already, and deletes it from the map, if the adjusted exponent is 0.) Then, the constructor that takes a list of units just calls adjustExponentBy(..., 1) for each element in the numerator, and calls adjustExponentBy(..., -1) for each element in the denominator.

Part 2: Extending Quantity.java (25 points) — Individual or Pair

The remaining methods we need in Quantity use a “database” telling us how to define some units in terms of others, e.g., a "day" is equivalent to exactly 24 hours. We represent this database using a Map<String,Quantity>. Units that appear as keys in this map are said to be "defined" units; units that are not keys in this map are said to be "primitive" units. A quantity is said to be normalized if the units it its map are all primitive. The definitions in the database might refer to other defined units (e.g., a day is 24 hours, an hour is 60 minutes, and a minute is 60 seconds). Here is some code to create a very simple map for test purposes; feel free to include this (or an extension of this) in your main:
    Map<String,Quantity> db = new HashMap<String,Quantity>();
    List<String> emp = new ArrayList<String>();  
                          // Sadly, Arrays.asList() gives an empty list of Objects
                          //   instead of an empty list of Strings.
    
    db.put("km", new Quantity(1000, 0, Arrays.asList("meter"), emp));
    db.put("day", new Quantity(24, 0, Arrays.asList("hour"), emp));
    db.put("hour", new Quantity(60, 0, Arrays.asList("minute"), emp));
    db.put("minute", new Quantity(60, 0, Arrays.asList("second"), emp));
    db.put("hertz", new Quantity(1, 0, emp, Arrays.asList("second")));
    db.put("kph", new Quantity(1, 0, Arrays.asList("km"), Arrays.asList("hour")));
(You will also need import java.util.*; or import java.util.Arrays at the top of your file.)

Here are the new methods to add: The code for normalizeUnit and normalize is surprisingly simple, since each can use the other (in addition to mul, div, and pow).

For normalizedUnit, either the given unit is primitive (in which case the answer is easy to construct), or it has a definition in the database that could be normalized.

Similarly, to normalize a quantity like 30 km^2 / hour, it suffices to observe that

  1. The products, quotients, and power of normalized quantities are normalized, and
  2. \[ \begin{array}{r@{\qquad}c@{\qquad}l} 30\ \mathrm{km}^2/\ \mathrm{hour} & = & \displaystyle 30\ \mathrm{km}^2/\ \mathrm{hour}\quad \times \quad \left(\frac{\mbox{1000 meter}}{\mathrm{km}}\right)^2 \quad \times \quad \frac{\mathrm{hour}}{\mbox{3600 second}}\\ \ \\ &=& \displaystyle \displaystyle 30\ \mathrm{km}^2\ \mathrm{hour}^{-1}\quad \times \quad \left(\frac{\mbox{1000 meter}}{\mathrm{km}}\right)^2 \quad \times \quad \left(\frac{\mbox{3600 second}}{\mathrm{hour}}\right)^{-1}\\ \end{array} \]
  3. The quantities “1000 meter” and “3600 meter” should look familiar from the discussion of normalizeUnit.

Submission: Submit your code as Quantity.java.


Totally Optional Extra Credit (up to 10 points) — Pair or Individual

Add a method convert to your Quantity class, that returns the result of transforming the specified quantity into specified units. For example, if we have a Quantity q representing 100 km/hour, then q.convert(...) with the right arguments should create a brand-new Quantity representing 1666.66667 meters/minute, and with other arguments should create a brand-new Quantity representing 27.77778 meter-hertz.

If the given quantity would not make sense in the requested units, throw IllegalArgumentException (including a helpful string describing the specific error).

In main, add several tests demonstrating your convert method in action.