Library Architecture

The NISTMonte library has been carefully structured for clarity, flexibility and efficiency.

NISTMonte contains many classes with many relatively simple methods. Each class has clearly defined responsibilities and a clear relationship with other classes. The code within each method is kept as short and focused as possible. Interfaces and abstract base classes are frequently used to enforce common behavior between different implementations of the same type of computation.

Events - Sources and Sinks

The class MonteCarloSS is responsible for tracking the electron's path. MonteCarloSS does not contain any code for modeling x-rays or for implementing detectors. Classes that implement detectors and x-ray production are loosely coupled to the MonteCarloSS class through an event mechanism. Using an event architecture is slightly less efficient than integrating detectors and x-rays into the Monte Carlo but an event architecture decouples the electron trajectory from the detectors. This tends to make the code for each component more focused and more clear.

An event architecture is an agreement between an event source (such as MonteCarloSS) and one or more event sinks (a detector or x-ray producer). Event sinks tell the event source that they are interested in receiving notification whenever something interesting happens at the event source. The event source is then responsible for notifying the event sink every time an interesting event occurs. For example, the event source MonteCarloSS notifies an event sync such as an detector whenever a scattering event occurs. The detector can then react to this event however it would like. One detector, like the TrajectoryImage class, plots onto a bitmap the electron trajectory segment since the last scattering event. Another, like the BackscatterStats class, ignores all events except events representing a backscattered electron. In the case of a backscattered electron, the BackscatterStats class tabulates statistics.

Event architectures are very flexible because

  1. an arbitrary number of sinks can be attached to a single source;
  2. the source need know nothing about the details of the sink;
  3. all sinks are independent of each other;
  4. new sinks can be added without modifying the source or existing sinks.

Once a programmer becomes comfortable with the idea of sources and sinks, it also becomes natural to consider that event sinks can also act as sources of new kinds of events. A sink can act as both a sink and a source. A sink might translate one kind of event into another kind of event which it passes on to a new level of sink. You might be tempted to call such an achitecture a multiple level event architecture.

NISTMonte uses a multiple level event architecture. Two common event sinks that can be attached to MonteCarloSS are the XRayEventListener and the BremsstrahlungEventListener. At each electron scattering point, these event sinks compute the generation of characteristic and Bremsstrahlung x-ray emission respectively. They do not tabulate the x-ray emission. Instead these sinks also act as event sources but sources of a different type of event – an x-ray emission event. Another level of event sink, x-ray detector sinks, such as the EmissionImage, the PhiRhoStats, the EDSDetector or the Microcalorimeter, tabulate the x-ray events in various different ways.

While this multilevel architecture might seem unnecessarily complex, it is actually quite efficient. Quantities are calculated only if necessary and only once. If we don't care about x-rays we don't attach x-ray event sinks and we don't waste time computing unused x-ray related quantities. However if we do care about x-rays we can compute the x-ray emission only once and tabulate it many times.

In addition, event architectures allow for separation of concerns. It is possible to add new event listeners without modifying the event source. This minimizes dependences between classes and makes code simpler to understand and more robust to enhancement. In computer science parlance, event based architectures scale better.

Interfaces and abstract base classes

In life as in software development, often there are many different ways to do the same thing. Starting at the same starting point there are many paths that will ultimately lead to the same (or similar) destination. The actual path taken can be less important that the result.

In software development, this situation is often compared to a black box. Inputs go into the black-box, something happens in the black box, and the black box pushes out a result. From outside of the black box you can't see what is happening in the black box. All that is known is that certain quantities go into the black box and a certain quantity comes out.

The simplest type of black box is a function with a specific argument set and a specific output type. A classic example is the sine function. The input is a double value representing an angle and the output is a double value representing the sine of the angle. It does not matter how the function transforms the input into the output (series expansion, look-up table, continued fraction expansion, ...) so long as the results is a sufficiently good approximation of the true sine. The Java library documentation specifies the standard library Math.sin contract as follows:

sin
public static double sin(double a)
Returns the trigonometric sine of an angle. Special cases:
  • If the argument is NaN or an infinity, then the result is NaN.
  • If the argument is zero, then the result is a zero with the same sign as the argument.
A result must be within 1 ulp of the correctly rounded result. Results must be semi-monotonic.
Parameters:
a - an angle, in radians.
Returns:
the sine of the argument.

Text 1: The Java library documentation (1.4.2) for the Math.sin function.

Part of this contract, the function signature, is enforced by the compiler - the argument must be a single double and the result a double. However part of this contract is enforced only by agreement between the library developer and the library user. The sin function strictly need not compute the sine. There is nothing stopping the library developer from implementing a function called sin that computes the cosine. Such a library probably wouldn't get used but from the compiler's perspective there is nothing invalid about this implementation. So the contract is partially enforced by the compiler and partially by documented convention.

The idea of a contract can be readily extended from functions to classes. Classes are simply one or more functions (also known as methods) bound to data. It is easy to imagine developing a contract in which a class is required to implement one or more functions. Part of this contract is enforced by the compiler through the definition of an interface. Part of this contract is a less formalized agreement between the library developer and library user. The later part of the contract is usually communicated between the developer and the user through programmer's documentation.

An interface is an organizational construct specifying zero or more function signatures but not the implementations. A class can declare that it implements an interface by identifying the interface and providing implementations for the functions specified in the interface contract. Multiple different classes can implement the same interface multiple different ways. Each class acts like a black box. The knobs on the outside of the box (the methods) are the same but the insides of the box (the implementation) are different.

Interfaces are used throughout the library to facilitate alternative implementations of algorithms. Abstract classes are similar to interfaces. In fact, you might say abstract classes are like a partially implemented interface. An abstract class is a class in which some of the member functions have been declared but have not been implemented. Classes derived from (based on) the abstract class are expected to implement the unimplemented member functions.

Abstract based classes are used extensively. Many times they are implemented as anonymous classes. Anonymous classes are very useful but a little obtuse. An example might be the best way to explain.

abstract class Operation {

protected String operation;

public Base(String op){
super();
operation = op;
}

public String getOperation(){ return operation; }

// The abstract method
public abstract int perform(int a,int b);

public static final Base Multiply = new Operation(“*”) {
public int perform(int a, int b) { return a*b; }
}

public static final Base Add = new Operation(“+”) {
public int perform(int a, int b) { return a+b; }
}
}

Text 2: An example of an abstract class and two anonymous implementations.

Text 2 shows an example class called Operation which declares but does not implement a function called perform which takes two integer arguments and returns a single integer. You can't create an instance of the class Operation directly because it is not fully defined. However, Multiply and Add represent two anonymous instances of the abstract base class Operation each of which implements the function perform differently. The items Multiply and Add are static instances of an anonymous class derived from Operation. They are referenced much like any other static member is referenced – Operation.Add or Operation.Multiply. Operation.Multiply.perform(2,3) would return 6 and Operation.Add.perform(2,3) would return 5.

Named instances of anonymous implementations of an abstract base class is very powerful and clear technique for implement different ways of computing the same quantity. Each implementation can be identified by a name which is descriptive of the algorithm. The instances can be readily interchanged by referring to the algorithm by the abstract base class name. This is an incredibly powerful technique for interchanging implementations of an algorithm. Text 3 is a trivial example that hints at how powerful the technique can be.

int function(Operation op){
return op.perform(2,3);
}

assert(function(Operation.Multiply)==6);
assert(function(Operation.Add)==5);

Text 3: An example of how named objects can represent algorithms using anonymous implementations of abstract based classes.