Implementation

Here, we describe implementation details of the Quantities package. The description is meant to be mainly textual. Additional information with direct reference to the code is included into the detailed documentation of classes, functions, and variables (see the files Quantity.h, Quantity.cc, QuantityError.h, Dimension.h, Unit.h, Prefix.h, Prefix.cc, Variable.h, Constant.h, UniqueConstant.h, Dynamic.h, Generic.h, QuantityAggregates.h, as well as files referenced in the implementation documentation of PhysicalQuantities and GeneralQuantities). For definitions of concepts, see Concepts. As a developer read Information for Developers.

The current version was implemented with the GNU C++ compiler, gcc, up to version 4.3.2. Some older compiler versions, notably those from the 3.3.x series will fail to compile the code.

The implementation of the quantity classes makes heavy use of the `two-level' aspect of C++ [citation Czernecki ...]. As far as possible, static (compile time) information is used about units This information is mainly represented by types manipulated by template metaprogramming techniques. On the other hand, occasionally dynamic access to such information is necessary, for example in routines that perform input from and output to users in a context which is only defined at run time. Such a situation is found e.g. when the user of the final program prescribes a unit for data input, or if a selected unit should be used for output. Then, the required information is only available in the run time situation, and type information can not be used statically.

Directory and File Structure

... welche Dirs? ...

Included Header Files

In most source code files of Quantities additional header files are included. In particular this is true for headers from the Loki and Boost headers. For the Loki headers, the loki `root' directory and the header file name are used in the include directives, e.g.
#include "loki/Typelist.h" 
The include-path which has to be given to the build process is then for example /usr/include. This would support transparency in the source code, by indicating clearly where the header comes from. In a similar way, the Boost headers should be treated.

The Build System

In the make doc target, an existing doxygen tag file BSUtilities.tag will be deleted upon invication. If the make doc process end normally, no such tag file will be retained.

Version Information

Version information is stored in header file version.h. QUANTITIES_VERSION is a macro set to a string which denotes the version of the current Quantities package. All functions returning version information retrieve this information from version.h.

The version number of Quantities consists of three numbers $x$, $y$, and $z$, separated by dots: $x$.$y$.$z$, for example 1.2.1. A plus sign $+$ may be appended to this number triple, indicating an interim version, which is being developed out of the version given.

Error Messages

Dimension

The seven components of dimension are rational numbers. They are represented as Rational<> templates (from BSUtilities, TemplateTools.h), parametrized by two long constants, a numerator and a denominator. This provides for exact arithmetics when performing algebraic calculations with the rational numbers, as long as the numerical limits of long objects are not exceeded. All calculations with these Rational<> numbers are performed at compile time.

The seven Rational<> objects are appended within a Loki::TypeList, which is retrieved from a Dimension object as a type DimList.

Moreover, the individual rational numbers are accessible ...

commensurablility ...

Unit

The Unit concept is implemented using types and static functions only. Unit objects do never need to be instantated.

... describe implementation of units ... ... describe implementation of unit low level functions ... ... compile time and run time functionality ...

Unit Symbol Grammar

The unit symbol grammar and the parse functionality of unit symbols for run time interpretation of symbol strings is implemented by means of the boost::spirit library. A unit symbol string for a non-prefixable and a prefixed unit can be interpreted quite simply. Only composed unit symbols have to be parsed using the grammar. ... composed grammar ... (note: there are some differences to the grammar for unit symbols given in the referenceManual - e.g. with respect to recursion of composed unit symbols; reason: the recursion is implemented by the recursion through the standardComposed und standard templates)

... alternative units ...

Quantity

The value, the name, and the symbol form the internal state of a quantity. The are represented by an object of the storage type (value), and by std::strings (namestring, symbolstring), respectively. In the present implementation, namestring and symbolstring are private members of the abstract Quantity base class, while the value is only defined in the concrete quantity classes being derived from this base class (Variable, Constant, UniqueConstant, see below The Mode). The latter is expected to change in future versions. Access to namestring and symbolstring depends on the mode of the quantity (see below).

Properties of a Quantity

... static and dynamic properties ...

The Quantity Type

... traits template ...

The Storage Type

... storage type ...

The Base Quantity

... base quantity ...

Quantity<QT, ST> classes form the abstract base classes for the definition of concrete quantity classes.

The Mode

... Variable, Constant, UniqueConstant ...

.. the DerivedQuantity class ... assembles information (1) as type (like SU) which should be set when using, and (2) as internal static data members (e.g., bool) which does not have to be set, but may rather be defined once and for all.

... the DerivedQuantityTraits traits template ...

... allows an unrestricted number of derived quantities from a single base quantity ...

... UniqueConstant: only one for a particular derived quantity. If several needed: have different DQTs and use same base quantity ...

The Storage Unit

... storage unit ...

The value of a quantity is strictly tied to the storage unit. It is not possible to access the value as a number alone without also getting some information about the associated unit, with only three exceptions:

These exceptions should only be used in cases where it is absolutely necessary, for example, when interfacing quantity objects with purely numerical data and calculations. Note, that the third exception above should not be used on a user level at all. Outside of Quantities code conversions should always be done through copy construction or assignment. Copy construction may implicitly be done in calls such as

MinuteTime tmin;
Time t (8.5);                // assumes s as storage unit: 8.5 s
tmin = t.value (Minute());   // converts t into a transient MinuteTime object
                             // for return and transforms the value accordingly

Dynamic Quantity Objects

... describe implementation of Dynamic variable objects ...

The Value

The Name and the Symbol

Names and symbols of quantities have default values, which are defined in the QuantityTraits and DerivedQuantityTraits traits templates. The definition of the values are provided by static functions NameString () and SymbolString (), rather than by static variables to avoid the `static initialization chaos' (or fiasco) (for further information, see http://www.parashift.com/c++-faq-lite/ctors.html#faq-10.13 and the FAQs following that one, or http://www.informit.com/blogs/blog.aspx?uk=30-C-Tips-in-30-Days-Tip-25-overcoming-static-initialization-dependencies .

The name and the symbol are derived at compile time on the basis of the default value given to the base quantity (as defined by the quantity type). The default can be overwritten with the respective value defined by the derived quantity type during a call to the derived quantity's constructor, if OverwriteName or OverwriteSymbol are set to true, respectively. This allows to give pre-defined values of name and symbol to different quantities derived from a single base quantity. The user may overwrite even these values with a call to a special constructor which expects two std::string arguments for the name and symbol, respectively, for Variable and Constant quantity objects. On the other hand, the name and the symbol of a UniqueConstant can not be changed, they are fixed by the definitions valid for the quantity and the derived quantity type.

All read access functions to name and symbol in any mode of a quantity are public. The respective values can thus be interrogated from everywhere.

In a Variable or a Constant, the write access functions to namestring and symbolstring are also public. Thus, the name and the symbol of these quantities can be changed after construction from a user program. In a UniqueConstant, these write access functions are redeclared protected, which prohibits changes of namestring and symbolstring.

Default values

... default values for value, name, symbol ...

... others? ...

Construction and Destruction

The default constructors
Variable (void)               // for a Variable
Constant (void)               // for a Constant
are public and generate an object of the respective type with a value of zero. The actual form of zero depends on the storage type. In the implementation a value of `0' is converted into the storage type. The numerical value of zero is assumed to be in the storage unit. The name and the symbol are automatically set to the default values by calling the Name and Symbol compile time functions. The default constructor of UniqueConstant is private. In contrast to the Variable and Constant default constructors it uses the default_value_ of the UniqueConstant type to initialize the value. This is the only constructor available for a UniqueConstant.

The constructors with a value

Variable (const ST &value)    // for a Variable
Constant (const ST value)     // for a Constant
store the numerical value (in the storage type ST) directly inside the generated quantity object. It is thus assumed that this value is in the storage unit. The name and symbol are initialized as mentioned for the default constructor.

The constructors with a value, a name and a symbol

Variable (const ST &value, const std::string &namestring, const std::string &symbolstring) // for Variable
Constant (const ST &value, const std::string &namestring, const std::string &symbolstring) // for Constant
additionally override the default name and string information in the QuantityTraits and DerivedQuantityTraits templates.

... constructor with a value and unit object ...

... constructor with a value, unit object, name and symbol ...

... constructor with value and unit symbol ...

... destruction ...

Copy Construction

... copy construction ...

... ok for quantities with same base unit; in general: commensurable quantities (same unit list!) ...

Copy construction from any other quantity object is only possible in specifically defined cases. No general copy constructor is defined for such an operation. If a quantity wishes to perform such a conversion, it has to provide the definition of a templated structure convertValue<>. The declaration of a general converting copy constructor which was available in earlier versions of Quantities has been removed after version 1.2.1.

On the other hand, such conversions can be defined to also allow different storage units and storage types. For example, when converting temperatures between ThermodynamicTemperature and CelsiusTemperature, the storage units and/or types of the two temperature types may be different. Note, however, that there may be numerical errors when converting between different storage types,

... special case UniqueConstant -> prevent sneaky copy ...

Assignment

Assignment operations are used to set a quantity object (target) to a state defined by some other object (source). In all cases, only values are copied, while the name and the symbol of the target object are not touched. Thus, assignment for quantity objects means assignment of the numerical value. This restriction is imposed onto the assignment operators owing to the fact that it is usually not intended to overwrite the name and symbol of a quantity, when assigning its value. Consider the following example:
Length l (2.);             // construct a length quantity object with value 2.0, the unit Metre
                           // without default values for  name and symbol
Length w;                  // define another length quantity object with default values
w.name ("width");          // set the name of object w
w.symbol ("w");            // set the symbol of object w
w = l;                     // assign l to w
If assignment would also take into account the name and the symbol of the source quantity, the assignment operation in the last line would reset the name and the symbols strings to their default values, as stored in object l. This is most probably not what is expected. A similar situation would arise if assignment is formulated from a GeneratedVariable, which has empty name and symbol strings, as in the following example:
Time t (1.);               // construct a time variable
Length l (2.);             // construct a length variable
Acceleration a;            // default construction of an acceleration variable
                           // has default name and symbol
a = l/(t * t);             // assignment of temporary GeneratedVariable to a
                           // default name and symbol of object a would be overwritten if
                           // assignment would also consider these variables

The only exception from this rule is the assignment to a target quantity from a C-style string or a std::string. Here, the interpretation of the input string can be modified by stream control objects to also extract a name and a symbol. However, in contrast to the former examples, in these cases the user of the quantity objects can deliberately select the behavior.

The implementation of assignment in Quantities is based on operator=. It provides several alternatives based on the nature of the source object. Some of these alternatives are not available for all modes of the target quantity. All assignments are available for a Variable quantity object as target. The Constant quantity object declares all assignment operators as private and UniqueConstants do not have assignment operators at all.

The most simple case is assignment between identical types of quantity objects. In this case, the two objects are tested for self assignment by comparing their addresses, and if different, the value of the source object is copied into the target object. No recalculation between units is needed, since the storage units of the two objects are identical. Commensurability does not need to be checked, since this identically typed obkects are always commensurable.

To assign from a quantity object with the same base quantity but possibly different storage type SST, storage unit SSU, and/or derived quantity type SDQT, operator= is overloaded with a template function

  template<template<typename, typename> class SM, typename SST, typename SSU, typename SDQT>
    Variable & operator= (const SM<Quantity<QT, SST>, DerivedQuantity<QT, SSU, SDQT> > &quantity)
which accepts a quantity in any mode SM as an argument (the source quantity). The formulation of the function's argument type prohibits assignment from an arbitray object. The return type of the function is always a Variable (target quantity), even if the source quantity was of another mode. The quantity types QT of the source and the target quantity are required to be identical. This ensures that recalculation of the value from the source storage unit SSU into the target storage unit SU is possible, but prohibits assignment from quantity objects having a base quantity which differs by more than the storage type, unit, and/or derived quantity type. Of course, the storage types ST and SST of target and source must be compatible, i.e. assignment of a value in SST to a variable of type ST must be allowed. Commensurability is guaranteed by the identical base quantities. If ST and SST, SU and SSU, and DQT and SDQT, respectively, were identical, the more specialized operator= discussed above would be used.

If assignment is attempted from a generated quantity object, commensurability must be checked, since this is no longer guaranteed. The overloading operator= is selected based on the form of the source quantity:

  template<typename SDIM, typename SST, typename SSU, typename SDQT>
    Variable & operator= (const Variable<Quantity<GenericClass<SDIM>, SST>,
                              DerivedQuantity<GenericClass<SDIM>, SSU, SDQT> > &quantity)
The source quantity being of mode Variable, the source quantity type is a GenericClass template, with the source dimension encoded by SDIM. Except for the case of a generated target quantity, the source storage unit SSU is always diffent from the storage unit of the target. The source storage Type SST may be different from the target storage type, subject to the same restriction as discussed in the previous assignment operator. The source value is recalculated into the target quantity's unit. In this process the commensurability check is performed by comparing the dimensions of the source and the target quantity. If this check fails, a compile time error DimensionError<True> is produced.

In special cases, assignment from a quantity object which has a different base quantity has to be allowed. An overloaded version of operator=

   template<template<typename, typename> class SM,
                     typename SQT, typename SST, typename SSU, typename SDQT>
     Variable & operator=
            (const SM<Quantity<SQT, SST>, DerivedQuantity<SQT, SSU, SDQT> > &quantity)
tries to call a copy constructor of the target object type with the source object as an argument. This does only succeed if such a conversion isexplicitly defined. Such assignments with conversion occur for example with the various temperature related physical quantities.

Assignment from a Dynamic quantity object requires special care. In order to allow for a storage types in the source Dynamic object different from that of the target quantity, the overloading member function is a template with the source storage type SST as parameter:

      template<typename SST>
        Variable & operator= (const Dynamic<SST> &dynamic)
The function checks commensurability at run-time with the Dynamic object's isCommensurable() member function, using *this as the argument. If the two objects are indeed commensurable, recalculation of the Dynamic object's value into the unit of the target Variable is done. This operation assumes that the Dynamic value is stored with a standardization ratio of unity. If the two objects are incommensurable, a DimensionMismatch exception is thrown.

Two overload versions of operator= for assignment from string types to a quantity type of mode Variable are defined. Both use the read_value member function to convert the string into a value and a unit. The unit is checked for being legally be used together with the target quantity. Assignment from a std::string or a const char * argument just forwards the string to read_value. The use of read_value automatically ensures that stream control objects are honored.

All assignments not listed above are prohibited. This is accomplished by declaring a most general overload of operator=

    template <typename T>
      Variable & operator= (const T &);
which is never defined. Thus, any attempt to instantiate an assignment from a T not discussed before, will result in a compile time error (e.g., `undefined reference' by gcc). In particular, assignment from a numerical value, like double, int, etc. is not allowed. This is an intended restriction, since it emphasizes the notion that a quantity always needs a unit attached to be meaningful.

Arithmetic Operations

The following simple arithmetic operations involving quantity objects are implemented:

... describe which combinations of types, separated formulations etc. ...

While the addition defined between two quantities is commutative in the sense that the two results of $a + b$ and $b + a$ always return true when compared by operator==, the types of the results can be different if a and b are of different type. The result of an addition is always a Variable, however, its storage type and storage unit are those of the quantity object on the left hand side of the addition operator.

Most operators return an object different from the objects operated on. The increment/decrement operators are exceptions from this rule.

The (prefix and postfix) increment and decrement operators are implemented according to Dryhurst's suggestions Dewhurst [Dewhurst_2003] (p. 129 and 264ff). In particular, the prefix operations return a reference to its objects, and the postfix operators return a const temporary. The latter prevents that later on the returned object can be assigned to. This would be useless, since the temporary is destroyed anyway. Increment and decrement operators are only implemented for Variable classes. The underlying value in Constant and UniqueConstant classes can not be changed, and, consequently, incrementing and decrementing them is not allowed.

Unary + and - operators are implemented to return an anonymous temporary object of the same type with no change in the case of unary + and only the sign changed in the case of unary -. Similar to the postfix increment and decrement operators, to avoid misuse of the returned temporary this is qualified as const. This mimics the behavior of the predefined unary + and - operators for built-in types (which do return an rvalue).

... exp, log, log10 ... Exponential, logarithmic, and trigonometric functions as defined in C++ are mirrored in the present code. The code needs to check that the argument of all of these functions is dimensionless. Also, the return value of these functions is a GeneratedVariable with all dimension components set to zero. It is thus also dimensionless.

The check for a dimensionless argument quantity is performed at compile time by the helper template CheckDimensionality, which is incorporated into a compile time IF structure. Depending on the outcome of CheckDimensionality, the compiler instantiates a helper function for the respective operation (if the quantity is dimensionless) or CheckDimensionality triggers a compiler error with a corresponding message (DimensionError<true>).

The return variable type is automatically generated using the GenerateVariable facilities by means of a multiplication of the argument quantity's dimension with a Rational<0>. This returns a dimensionless GeneratedVariable type. This construction allows to assign the result of the function to be assigned to another dimensionless variable (e.g., the PhysicalQuantity PureNumber).

Since the functionality described is identical for all the exponential, logarothmic and trigonometric functions, and the necessary code differs only in the name of the respective function in namespace std to be called (and the name of the helper function), the code generation was wrapped into a macro QUANTITY_VARIABLE_DIMENSIONLESS_MATHFCT(function, helper). The two macro arguments are the names of the namespace std funtion and the helper function, e.g., `exp' and `expHelper'. This allows to avoid repetition of almost identical code. In fact, each function can be programmed as a one-line macro invocation.

The trigonometric functions are specific in the sense that their namespace std operations expect their argument to be in radians. As a result, in general, it is assumed that code as

Time t1 (5.);                 // t1 = 5 s
Time t2 (2.);                 // t2 = 2 s
PureNumber n = sin (t1/t2);   // argument of sin is dimensionless: 2.5
will take the sin of the numerical value of the dimensionless quantity generated by the division of two Time quantities. No recalculation is performed in general.

An exception is the PhysicalQuantities PlaneAngle class, which defines specializations of all trigonometric helper functions. These prescribe the recalculation of the value in the PlaneAngle object into unit Radian, even if it is stored in a different PlaneAngle unit.

Comparison

Comparison operations are defined for any mode of quantities by a macro QUANTITY_COMPARISON_OPERATORS, which takes three arguments: the mode, the operator name and the operator type. Comparison is implemented for Variable, Constant, and UniqueConstant modes separately. The operator name and type cary duplicated information. However, they have to be formulated separately, since the operator name (e.g. operator==) can not be generated by concatenation from the operator type (e.g., ==) within the C++ macro preprocessor. The use of macros was deemed necessary in this case, since the code for each operator type and each mode variant of the derived quantities is almost identical (apart from the macro parameters mentioned above. For six comparison operators types and three modes this results in 18-fold replication of code, and 18-fold repetition of the same code changes if some variation in the code is attempted.

Comparison involves two objects: a left hand side and a right hand side object. Four overloaded functions are defined for each comparison operator in a particular mode:

Any other comparison between objects not listed above triggers a VariableError<true> compiler message.

Each QUANTITY_COMPARISON_OPERATORS macro invocation expands into (presently) four overloading member functions for different types of right hand side objects.

The following comparison operators are defined:

Serialization

Within Quantities, serialization is implemented through the Boost.Serialization library [BoostSerialization]. Consequently, code attempting to serialize a Quantity object must be prepared to include the appropriate boost headers. No assumption about the type of archives is made in Quantities. The example programs in Quantities use XML archives. However, in user code, other types of archives may be used, as provided by the boost library or by user definition.

UniqueConstant objects can not be serialized, and do not need to be. Their value is stored and fixed upon first construction of the object. Thus, when the object is generated when re-reading the serialized form, all it's contents is already available. Consequently, there is no use to serialize this type of a Quantity.

Helper Constructs

... helper constructs, such as CheckDimensionality ...

Physical Quantities

Here, we describe implementation details of the PhysicalQuantities part of the Quantities package. The description is meant to be mainly textual. Additional information with direct reference to the code is included into the detailed documentation of classes, functions, and variables

The current version was implemented with the GNU C++ compiler, gcc, up to version 4.3.0. Some older compiler versions, notably those from the 3.3.x series will fail to compile the code.

PhysicalQuantitiese provides header files (*.h) and implementation files (*.cc) for each physical quantity. Moreover to allow serialization, export code is given in files *_export.h. The headers and expot files should be included into user code as appropriate. If the conversion between temperature values in different scales is attempted, also header TemperatureConversions.h must be included.

Physical quantities are implemented as template specializations of Quantity<> derived Variable<>, Constant<> or UniqueConstant<> types. In particular, the quantity type is specified, and for each quantity type a QuantityTraits<> traits template is provided. In this template, the Dimension, the UnitType, the UnitList, and the DefaultUnit of the physical quantity are specified.

Fundamental physical constants are implemented as UniqueConstants. Some of them are additionally defined physical quantities with a separate quantity type (e.g., FaradayConstant), others, however, are just UniqueConstants derived from an already existing Quantity<> (e.g., ElementaryCharge is a UniqueConstant using the electricCharge::Quantity base class.

Mathematical Quantities

...

General Quantities

...

Quantity Aggregates

...

QuantityVector

VariableVector

VariableVectorIterator

The QuantityVector Class

The quantity vector object should behave mostly like a std::vector, but have additional information about the units and transformations of the quantity values stored.

There are two possible solutions to implement this:

The first solution leads to slicing problems (see, Dewhurst [Drewhurst_2003] and Sutter/Alexandrescu [Sutter/Alexandrescu_2005]), i.e. assignment of the quantity aggregate to the unerlying std::vector<ST> is possible with concommittant loss of information. The second solution is probably more tedious, because much functionality of the std::vector<> must be reimplemented or made available in passthrough functions. ...

The VariableVector Class

...

References

[Dewhurst_2003] S.C. Dewhurst, C++ Gotchas, Addison-Wesley, Boston, 2003.

[BoostSerialization] http://www.boost.org/doc/libs/1_35_0/libs/serialization/doc/index.html

[Sutter/Alexandrscu_2005] H. Sutter and A. Alexandrescu, C++ Coding Standards, Addison-Wesley, Boston, 2005.

back to Quantities start page


Generated on Wed Apr 11 18:07:08 2012 for Quantities by  doxygen 1.5.6