Developer Notes

Part I: Good

C Opaque Type as an Object

In the C language, object-oriented abstraction can be implemented using structures and functions. In C, a pointer to a structure can be defined without disclosing the elements of the structure. For example, in the header file Shape.h we define opaque type

typedef const struct __Shape * ShapeRef;

This declares a new type, ShapeRef, which is a pointer to a hidden structure __Shape. While the messages we can send to (or functions we can call with) this structure pointer are defined in a C header file, i.e. Shape.h, we are not allowed to see nor access directly the elements of this structure.

You can read more about Opaque data types and Opaque pointers at Wikipedia. You can also learn more about Opaque types at Apple.

Mutable vs Immutable Types

You may have noticed in the example on Opaque Types that the pointer was defined using the const keyword. The keyword const means that the contents of __Shape structure cannot be modified after it is created. This makes Shape an immutable type. This means that while we can define functions that reveal the internal state (i.e., structure elements) of Shape, we cannot define functions that change its internal state. Only by eliminating the keyword const in the type definition would we be able to define functions to change the internal state of the type. A type whose internal state can be modified after creation is called a mutable type. Thus, in the header file Shape.h we also define the opaque type

typedef struct __Shape * MutableShapeRef;

What are the advantages of an immutable type? Let's say you want to use an instance of type A as a variable inside type B. You decide to do this by placing a copy of the pointer for the instance of A inside B. Unbeknownst to you, however, another type, let's call it C, also has a copy of the pointer for the same instance of A. If A was mutable, then type C could change the value of A, and, in turn, change the instance variable A inside B, without B knowing what happened. There are two ways to handle this problem. If A is mutable, then type B has no choice but to make its own personal copy of the A instance. Depending on the size of A, this could be a memory and time consuming process. On the other hand, if A is immutable, then B can safely (and rapidly) copy only the pointer for A. As you might imagine, immutable types are highly useful in concurrent programs. General advice: don't define an type as mutable unless you really need it.

Read more about Mutable and Immutable types at Wikipedia.

Implementation of Shape

Now, looking inside the source Shape.c, we find the structure

struct __Shape {
    // Shape Type attributes  - order of declaration is essential
    float xPosition;
    float yPosition;
    float orientation;
};

Creation and Destruction of the Shape Type is handled with these function:

static struct __Shape *ShapeAllocate()
{
      struct __Shape *theShape = malloc(sizeof(struct __Shape)); 
      if(NULL == theShape) return NULL;
      return theShape;
}

ShapeRef ShapeCreate(float xPosition, float yPosition, float orientation)
{
     struct __Shape *newShape = ShapeAllocate(); 
     if(NULL == newShape) return NULL; 
     newShape->xPosition = xPosition; 
     newShape->yPosition = yPosition;           
     newShape->orientation = orientation;
     return newShape;
}

void ShapeFinalize(ShapeRef theShape)
{
     if(NULL == theShape) return;
     free((void *)theShape); 
}

Comparison and Accessors are handled with these functions:

bool ShapeEqual(ShapeRef theShape1, ShapeRef theShape2) 
{
     if(NULL == theShape1 || NULL == theShape2) return false; 
     if(theShape1 == theShape2) return true;
     if(theShape1->xPosition != theShape2->xPosition) return false; 
     if(theShape1->yPosition != theShape2->yPosition) return false; 
     if(theShape1->orientation != theShape2->orientation) return false; 
     return true;
}

float ShapeGetXPosition(ShapeRef theShape)
{
     if(NULL == theShape) return nan(NULL);
     return theShape->xPosition;
}
void ShapeSetXPosition(MutableShapeRef theShape, float xPosition) {
     if(NULL == theShape) return;
     theShape->xPosition = xPosition;
}

A shape can be translated and rotated. These methods are handled with these functions:

void ShapeTranslate(MutableShapeRef theShape, float xTranslation, float yTranslation) 
{
     if(NULL == theShape) return;
      theShape->xPosition += xTranslation; 
      theShape->yPosition += yTranslation;
}

void ShapeRotate(MutableShapeRef theShape, float angle) 
{
     if(NULL == theShape) return;
     theShape->orientation += angle;
}

Usage of Shape

    MutableShapeRef shape = ShapeCreateMutable(0.0, 0.0, 0.0);
    ShapeShow(shape);
    
    ShapeTranslate(shape, 10.0, 20.0);
    ShapeRotate(shape, 180.);
    ShapeShow(shape);
    ShapeFinalize(shape);

Inheritance

Let's examine how we can define a Square type that inherits from Shape. In source code we define the structure

struct __Square {
     // Shape Type attributes - order of declaration is essential 
     float xPosition;
     float yPosition;
     float orientation;
     // Square Type attributes
     float width; 
};

For this inheritance trick to work it is essential that the order of instance variable declarations be identical to those inside the Shape structure. Any additional instance variables must go after the variables matching Shape's structure.

In the header file we define the opaque types

typedef const struct __Square * SquareRef;
typedef struct __Square * MutableSquareRef;

Creation and Destruction of the Square Type is handled with these function:

static struct __Square *SquareAllocate() 
{
     struct __Square *theSquare = malloc(sizeof(struct __Square)); if(NULL == theSquare) return NULL;
     return theSquare;
}

SquareRef SquareCreate(float xPosition, float yPosition, float orientation, float width) 
{
     struct __Square *newSquare = SquareAllocate();
     if(NULL == newSquare) return NULL; 
     newSquare->xPosition = xPosition; 
     newSquare->yPosition = yPosition; 
     newSquare->orientation = orientation; 
     newSquare->width = width;
     return newSquare; 
}

void SquareFinalize(SquareRef theSquare) 
{
     if(NULL == theSquare) return;
     free((void *)theSquare); 
}

Comparison and Accessors are handled with these functions:

bool SquareEqual(SquareRef theSquare1, SquareRef theSquare2) 
{
     if(!ShapeEqual((ShapeRef) theSquare1, (ShapeRef) theSquare2)) return false;
     if(theSquare1->width != theSquare2->width) return false;
     return true;
}

float SquareGetXPosition(SquareRef theSquare) 
{
     return ShapeGetXPosition((ShapeRef) theSquare); 
}

float SquareGetWidth(SquareRef theSquare) 
{
     if(NULL == theSquare) return nan(NULL);
     return theSquare->width;
}

void SquareSetXPosition(MutableSquareRef theSquare, float xPosition) 
{
     ShapeSetXPosition((MutableShapeRef) theSquare, xPosition); 
}

void SquareSetWidth(MutableSquareRef theSquare, float width) 
{
     if(NULL == theSquare) return;
     theSquare->width = width; 
}

Notice how we type cast a Square into a Shape before calling Shape methods.

Usage of Square

    MutableSquareRef square = MutableSquareCreate(0.0, 0.0, 0.0, 10.0);
    ShapeShow((ShapeRef) square);
    SquareShow(square);
  
    ShapeTranslate((MutableShapeRef) square, 10.0, 20.0);
    ShapeRotate((MutableShapeRef) square, 180.);
    SquareShow(square);
    SquareFinalize(square);

Source code used in this section:

Shape.c
Shape.h
Square.c
Square.h
main.c

Part II: Better

In the last section we examined how Opaque types in C can be adapted to follow some Object Oriented Design Patterns. It is a sensible approach but it still lacks memory management, collections, strings, and many other useful object-oriented design features. To continue on our path towards a more complete approach let's look how we can introduce retain count memory management.

Reference Counting

When an type is no longer needed it should be deallocated and its memory freed. But how will a type know when it's no longer needed? Type A may hold a reference (pointer) to type B, but how does type A know that type B still exists? For example, type B could have initially been created to be part of type C. If type C gets destroyed along with all it's constituent objects and type A doesn't know, then type A could end up sending a message to (calling a function with) a non-existent type B, and crash the program.

The solution we adopt to solve this problem is called reference counting. When a type wants to hold a reference to another type it calls that type's retain function. Every time an type's retain function is called, it increments its internal retainCount variable. Conversely, when an type no longer needs to hold a reference to a type it calls that type's release function. Every time an type's release function is called, it decrements its internal retainCount variable. When a type's retainCount hits zero, then the type self destructs. That is, it would call the release function of any types it had retained, and then deallocate itself.

With this in mind, we follow the conventions below.

  • if you create an type (either directly or by making a copy of another type—see “The Create Rule”), you own it. We will explicitly use the word Create or Copy in the name of any function that creates and returns a type with a retain count of 1.
  • if you get an type from somewhere else, you do not own it. If you want to prevent it being disposed of, you must add yourself as an owner (using a retain method).
  • if you are an owner of an type, you must relinquish ownership when you have finished using it (using a release method).

Read more about Reference counting at Wikipedia and at Apple.

Implementation

We begin by creating a fundamental opaque type called KFType, from which all other types will inherit. In KFType source code define structure

struct __KFType {
     u_int32_t retainCount;
     void (*finalize)(void *); 
     bool (*equal)(void *, void *);
};

In KFType header define opaque type

typedef struct __KFType * KFTypeRef;
KFType Methods
bool KFTypeEqual(KFTypeRef theType1, KFTypeRef theType2)
{
return theType1->equal(theType1,theType2);
}

void KFRelease(KFTypeRef theType)
{
     if(NULL==theType) return;
     if(theType->retainCount == 1) {
          theType->finalize(theType); return; 
     }
     theType->retainCount--;
     return;
}

KFTypeRef KFRetain(KFTypeRef theType)
{
     if(NULL==theType) return NULL;
     theType->retainCount++;
     return theType;
}
Now we can define KFShape to inherit from KFType
struct __KFShape {
     u_int32_t retainCount;
     void (*finalize)(void *); 
     bool (*equal)(void *, void *);

     // Shape Type attributes
     float xPosition; 
     float yPosition; 
     float orientation;
};

static struct __KFShape *KFShapeAllocate()
{
     struct __KFShape *theShape = malloc(sizeof(struct __KFShape)); 
     if(NULL == theShape) return NULL;
     theShape->retainCount = 1;
     theShape->finalize = KTShapeFinalize;
     theShape->equal = KFShapeEqual;
     return theShape;
}

Usage of KTShape

    KFMutableShapeRef shape = KFShapeCreateMutable(0.0, 0.0, 0.0);
    KFShapeShow(shape);
    
    KFShapeTranslate(shape, 10.0, 20.0);
    KFShapeRotate(shape, 180.);
    KFShapeShow(shape);
    
    KFRelease((KFTypeRef) shape);

Source code used in this section:

KFType.c
KFType.h
KFShape.c
KFShape.h
main.c

Part III: Best - Core Foundation

At this point, if you know anything about object-oriented programming, you are thinking Yeah, but you would still have to create types like KFSet, KFArray, KFDictionary, KFString, etc. Well, that is true. The good news, however, is that Apple has done all that for us. Starting with their fundamental CFType, Apple has built CFSet, CFArray, CFDictionary, CFString, and many, many other wonderful types. Reference counting is also the approach employed in Core Foundation.

Even more wonderful is that Apple's Core Foundation framework is open source and a version called Core Foundation Lite can be used to write cross-platform applications for Mac OS X, Linux, and Windows (via Cygwin).

How do I make my own C type that inherits from CFType?

Simplest Solution : Use a CFDictionary and place all instance variables into dictionary.

This is easy if your instance variables (ivars) can be readily defined with other Core Foundation Types, e.g., CFNumber, CFString, etc. but adds overhead particularly when you have to wraps many int’s and float’s into CFNumbers.

Roll your own CFType

Alternatively, you can create your own type that inherits from CFType and takes full advantage of the Core Foundation library. Inside the source for our Shape is a structure that inherits from CFType.

#include <CoreFoundation/CoreFoundation.h>
#include "CFRuntime.h"

struct __CFShape {
    CFRuntimeBase   _base;

    // Shape Type attributes
    float xPosition;
    float yPosition;
    float orientation;
};

Notice that the first line of every CFType structure is CFRuntimeBase _base;. This is defined in the file CFRuntime.h, which can be found at the bottom of this page. This file needs to be included in your project if you plan to create your own CFType.

Inside the header file for CFShape we define the types.

typedef const struct __CFShape * CFShapeRef;
typedef struct __CFShape * CFMutableShapeRef;

Also inside the source we use the following code for allocating and creating a CFShape.

// Static variable that gets assigned a unique value when the first CFShape is created.
static CFTypeID _kCFShapeID = _kCFRuntimeNotATypeID;

// CFRuntimeClass variable defining our CFShape type for Core Foundation.
static CFRuntimeClass _kCFShapeClass = {
    0,
    "CFShape",
    NULL,
    NULL,
    CFShapeFinalize,
    CFShapeEqual,
    NULL,
    NULL,
    CFShapeCopyDebugDesc};

// Allocate CFShape.
static struct __CFShape *CFShapeAllocate()
{
    // First time a CFShape is allocated, so we need to register the type with Core Foundation
    if(_kCFShapeID ==_kCFRuntimeNotATypeID) {
        _kCFShapeID = _CFRuntimeRegisterClass((const CFRuntimeClass * const)&_kCFShapeClass);
    }

    // Allocate a CFShape
    struct __CFShape *theShape;
    uint32_t extra = sizeof(struct __CFShape) - sizeof(CFRuntimeBase);
    theShape = (struct __CFShape *)_CFRuntimeCreateInstance(kCFAllocatorDefault, _kCFShapeID, extra, NULL);
    if (NULL == theShape) return NULL;
    return theShape;
}

CFShapeRef CFShapeCreate(float xPosition, float yPosition, float orientation)
{
    struct __CFShape *newShape = CFShapeAllocate();
    if(NULL == newShape) return NULL;
    newShape->xPosition = xPosition;
    newShape->yPosition = yPosition;
    newShape->orientation = orientation;
    return newShape;
}

CFMutableShapeRef CFShapeCreateMutable(float xPosition, float yPosition, float orientation)
{
    return (CFMutableShapeRef) CFShapeCreate(xPosition, yPosition, orientation);
}

Usage of CFShape

Here's an example to match our previous examples.

    CFMutableShapeRef shape = CFShapeCreateMutable(0.0, 0.0, 0.0);
    CFShapeShow(shape);
    
    CFShapeTranslate(shape, 10.0, 20.0);
    CFShapeRotate(shape, 180.);
    CFShapeShow(shape);
    
    CFRelease(shape);

Things get more exciting, however, when you bring all the rest of Core Foundation's various types to bear in your code. Here's an example using CFArray.

    CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
    for(float orientation = 0.0; orientation <360.; orientation += 45.0) {
        CFMutableShapeRef shape = CFShapeCreateMutable(0.0, 0.0, orientation);
        CFShapeShow(shape);
        CFArrayAppendValue(array, shape);
        CFRelease(shape);
    }
    
    printf("\n\n");
    CFIndex count = CFArrayGetCount(array);
    for(CFIndex index = 0; index< count; index++) {
        CFMutableShapeRef shape = (CFMutableShapeRef) CFArrayGetValueAtIndex(array, index);
        CFShapeShow(shape);
    }
    
    CFRelease(array);

Just wait, it gets even better. You can even use your CFType as an object in Objective-C. Here's a translation of the code above into objective C.

    @autoreleasepool {
        
        NSMutableArray *array = [[NSMutableArray alloc] init];
        for(float orientation = 0.0; orientation <360.; orientation += 45.0) {
            CFMutableShapeRef shape = CFShapeCreateMutable(0.0, 0.0, orientation);
            CFShapeShow(shape);
            NSLog(@"retain count is %ld",[(id) shape retainCount]);
            [array addObject:(id) shape];
            NSLog(@"retain count is %ld",[(id) shape retainCount]);
            [(id) shape retain];
            NSLog(@"retain count is %ld",[(id) shape retainCount]);
            [(id) shape release];
            CFRelease(shape);
        }
        
        printf("\n\n");
        NSUInteger count = [array count];
        for(NSUInteger index = 0; index< count; index++) {
            CFMutableShapeRef shape = (CFMutableShapeRef) [array objectAtIndex:index];
            CFShapeShow(shape);
        }
        
        [array release];
    }

While we still create our shape with the C method, we can put our shape into an NSArray and get it out with no problems. We can retain and release our shape as if it was an objective C object. And our shape even gets released when the array containing it is released.

One final note: I would suggest that you don't use CF as the first two characters of the name of your own CFTypes. Leave those for Apple.

Source code used in this section:

CFRuntime.h
CFShape.c
CFShape.h
main.c
main.m

PhySy

PhySy is written in C using object-oriented design concepts. This design is modeled after Apple's Core Foundation framework. PhySy Foundation is also built on top of the Core Foundation framework.

Architecture

PhySy Foundation

The PhySy Foundation framework provides support for the SI Units of quantities in the physical sciences.

An important characteristic of physical quantities is that any given physical quantity can be derived from other physical quantities through physical laws. For example, the physical quantity of speed is calculated as a ratio of distance traveled to time elapsed. The volume of a box is calculated as the product of three quantities of length: i.e., height, width, and depth of the box. Any physical quantity can always be related back through physical laws to a smaller set of reference physical quantities. This idea was originally proposed by Joseph Fourier in his book Théorie analytique de la chaleur (The Analytic Theory of heat). In fact, as the laws of physics become unified it has been argued that this smaller set can be reduced to simply the Planck length and the speed of light. At the level of theory employed by most scientists and engineers, however, there is a practical agreement that seven physical quantities should serve as fundamental reference quantities from which all other physical quantities can be derived. These reference quantities are (1) length, (2) mass, (3) time, (4) electric current, (5) thermodynamic temperature, (6) amount of substance, and (7) luminous intensity.

In the next parts we examine the three important types in PhySy Foundation: PSScalar, PSUnit, and PSDimensionality. Ultimately, programmers will have more significant interactions with a type like PSScalar than PSUnit, and even less than with PSDimensionality. Therefore, we begin this tutorial with the PSScalar type.

PSScalar

PSScalar represent a scalar physical quantity, and has two essential attributes: a numerical value and a unit. It supports four element types: float, double, float complex, and double complex.

One simple method for creating a PSScalar is to parse a string input using the method shown below:

PSScalarRef acceleration = PSScalarCreateWithCFString(CFSTR("9.8 m/s^2"));
PSScalarShow(acceleration);

The CFSTR() method is a Core Foundation macro for creating constant compile-time strings. The output of this code is the value of the scalar.

9.8 m/s^2

We can define another scalar and divide by our previous scalar,

PSUnitRef newtons = PSUnitFindWithName(CFSTR("newtons"));
PSScalarRef force = PSScalarCreateWithDouble(500,newtons);
PSScalarRef mass = PSScalarCreateByDividing(force, acceleration);
PSScalarShow(mass);
in code that displays
51.02041 kg

Unit conversions are straightforward. One approach, given below,

PSUnitRef grams = PSUnitFindWithName(CFSTR("grams"));  
PSScalarConvertToUnit((PSMutableScalarRef) mass, grams);
PSScalarShow(mass);

displays
51020.41 g

Let's consider a problem familiar to chemistry students.

What pressure in atmospheres does 0.078 moles of hydrogen exert on the walls of a 42.0 mL container at 25.0 °C?
Using the ideal gas equation of state, $p V = n R T$, we identify the variables

n is # moles = 0.078 mol
T is Temperature in Kelvin = 273.15 K+25.0 K = 298.15 K
V is Volume = 42.0 mL
R is Gas Constant = 8.314510 J/(K mol)

and solve for the pressure: $ p = n R T/V$. The solution can be coded as follows.

PSScalarRef n = PSScalarCreateWithCFString(CFSTR("0.078 mol"));
PSScalarRef T = PSScalarCreateWithCFString(CFSTR("298.15 K"));
PSScalarRef V = PSScalarCreateWithCFString(CFSTR("42.0 mL"));
PSScalarRef R = PSScalarCreateWithCFString(CFSTR("8.314510 J/(K*mol)"));
PSMutableScalarRef p = (PSMutableScalarRef) PSScalarCreateByMultiplying(n, R);
PSScalarMultiply(p, T);
PSScalarDivide(p, V);
PSScalarShow(p);
    
CFRelease(n);
CFRelease(T);
CFRelease(V);
CFRelease(R);
CFRelease(p);
The output from this code is
4603803.67339444  Pa

Alternatively, the entire calculation can be done inside the string input,

PSScalarRef p = PSScalarCreateWithCFString(CFSTR("0.078 mol * 8.314510 J/(K*mol) * 298.15 K/(42.0 mL)"));
PSScalarShow(p);
with identical output.
4603803.67339444  Pa

Let's not forget that the problem requested the final pressure in units of atmospheres. This can be readily obtained with the code below,

PSUnitRef atmospheres = PSUnitFindWithName(CFSTR("atmospheres"));
PSScalarConvertToUnit((PSMutableScalarRef) p, atmospheres);
PSScalarShow(p);
which to get the answer in the correct units.
45.43574381963646  atm

See the full API of PSScalar here.

PSScalar is also a concrete subtype of PSQuantity, whose full API can be found here. For example, if you want to get the unit in a PSScalar you can call the parent type method, PSQuantityGetUnit, according to

PSUnitRef unit = PSQuantityGetUnit( (PSQuantityRef) p);
PSScalarShow(unit);
which displays the units of p.
atm

Another concrete subtype of PSQuantity is PSBlock, which represents a block of physical quantities with the same unit. The API for PSBlock can be found here.

PSUnit

Units

Inherent in the measurement of any physical quantity is a comparison to a previous measurement. What is most useful is the ratio of the new measurement to a previous measurement. For example, an ancient scientist might have used a cubit to measure the ratio of large tree trunk's circumference to its diameter. Around 3000 B.C.E., a cubit was decreed to be the length of a forearm and hand. So, a scientist could make a string of 1 cubit in length using the distance from the back of her elbow to the tip of her middle finger, and then use the string to measured the ratio \begin{equation} \frac{l_\text{circumference}} {l_\text{diameter}} = 3.14. \end{equation} While another scientist with longer arms might have cut a longer string to be a cubit, the procedure for finding the ratio of large tree trunk's circumference to its diameter will be the same, and the result is independent of the absolute length of the string used, i.e., independent of the units of length used.

We will represent a physical quantity, $q$, using the notation \begin{equation} q = \{ q \} \cdot [q], \end{equation} where $\{ q \}$ is the numerical value and $[q]$ is the reference unit.

Coherent SI Base Units

The coherent SI base (reference) units form a set of seven units, and are given below:

Base Dimension Name Plural Name Symbol
length meter meters m
mass kilogram kilograms kg
time second seconds s
electric current ampere ampere A
thermodynamic temperature kelvin kelvin K
amount of substance mole moles mol
luminous intensity candela candelas cd

The coherent SI base (reference) units form a set of seven units, described in table above and given by the symbols \begin{equation} [q]_\text{CBU} \in [Q]_\text{CBU} = \left\{ \mbox{m}, \mbox{kg}, \mbox{s}, \mbox{A}, \mbox{K}, \mbox{mol}, \mbox{cd} \right\}. \end{equation}

SI Base Root Units

A minor complication in designing our model is that the coherent base unit for mass in SI Units is defined as the kilogram and not the gram. For this reason, we define a set of seven base root units, \begin{equation} [q]_\text{BRU} \in [Q]_\text{BRU} = \left\{ \mbox{m}, \mbox{g}, \mbox{s}, \mbox{A}, \mbox{K}, \mbox{mol}, \mbox{cd} \right\}. \end{equation} with names and symbols shown in the table below.

Base Dimension Name Plural Name Symbol
length meter meters m
mass gram gram g
time second seconds s
electric current ampere ampere A
thermodynamic temperature kelvin kelvin K
amount of substance mole moles mol
luminous intensity candela candelas cd

SI Base Units

The set of Coherent SI Base Units only includes the seven SI base units. The larger set of SI Base Units includes the Coherent SI Base Units as well as all decimal multiples of the root units created using the 20 prefix symbols given in the table below with the root unit names and symbols given in the table immediately above. These prefixed and unprefixed symbols form a set, $[Q]_\text{BU}$, of 147 SI base units, \begin{equation} [q]_\text{BU} \in [Q]_\text{BU} = \left \{ x_L\mbox{m}, x_M\mbox{g}, x_T\mbox{s}, x_I\mbox{A}, x_\Theta\mbox{K}, x_N\mbox{mol}, x_J\mbox{cd} \right \}, \end{equation} where $[Q]_\text{CBU} \subseteq [Q]_\text{BU}$, $[Q]_\text{BRU} \subseteq [Q]_\text{BU} $, and $x_i$ indicates that the root unit symbol may be modified with one of the SI prefixes given in the table below.
SI Prefix Name x, SI Prefix Symbol factor SI Prefix Name x, SI Prefix Symbol factor
yotta Y $10^{24}$ yocto y $10^{-24}$
zetta Z $10^{21}$ zepto z $10^{-21}$
exa E $10^{18}$ atto a $10^{-18}$
peta P $10^{15}$ femto f $10^{-15}$
tera T $10^{12}$ pico p $10^{-12}$
giga G $10^{9}$ nano n $10^{-9}$
mega M $10^{6}$ micro $\mu$ $10^{-6}$
kilo k $10^{3}$ milli m $10^{-3}$
hecto h $10^{2}$ centi c $10^{-2}$
deca da $10^{1}$ deci d $10^{-1}$

In PhySy, one can obtain any of these units its symbol and the function

PSUnitRef massUnit = PSUnitForSymbol(CFSTR("kg"),NULL);

Here CFSTR(const char *cStr) is a Core Foundation function that creates an immutable string from a constant compile-time string. The second argument, set to NULL in this case, is a pointer to a double holds a multiplier, if needed, for converting between the unit whose symbol is passed in the string argument and the unit returned. In cases such as this one, where a multiplier is clearly not needed, then a NULL pointer can be safely passed to the function. Examples where a multiplier will be necessary are given later in this section.

Coherent Derived SI Units

Coherent derived SI units are an infinite set defined as the products of powers of coherent SI base units for all positive and negative integer values of the exponents.

PSUnitRef forceUnit = PSUnitForSymbol(CFSTR("kg*m/s^2"),NULL);

PSDimensionalityRef pressureDimensionality = PSDimensionalityForQuantity(CFSTR("pressure"));
PSUnitRef pressureUnit = PSUnitCoherentSIUnitForDimensionality(pressureDimensionality);

Derived SI Units

Derived SI units are an infinite set defined as the products of powers of SI base units for all positive and negative integer values of the exponents.

PSUnitRef forceUnit = PSUnitForSymbol(CFSTR("g*m/s^2"),NULL);

PSUnitRef forceUnit = PSUnitForSymbol(CFSTR("mg*cm/ps^2"),NULL);

Equivalent Units

While all units in the set $[Q]_\text{DU}$ have unique derived symbols and names, it should be noted that some are functionally equivalent. For example, a derived physical quantity such as speed in units of $[\text{m}~\text{s}^{-1}]$ can be converted amongst any of the units below, \begin{equation} [\text{m}~\text{s}^{-1}] \equiv [\text{km}~\text{ks}^{-1}] \equiv [\text{hm}~\text{hs}^{-1}] \equiv [\text{dam}~\text{das}^{-1}] \equiv [\text{dm}~\text{ds}^{-1}] \equiv [\text{cm}~\text{cs}^{-1}] \equiv [\text{nm}~\text{ns}^{-1}], \end{equation} without modifying the numerical value of the quantity.

The function PSUnitAreEquivalentUnits can be used to test whether two units are equivalent, and can be substituted for each other without modifying the numerical value of the quantity. In the example code below, the function

PSUnitRef unit1 = PSUnitForSymbol(CFSTR("m/s"),NULL));
PSUnitRef unit2 = PSUnitForSymbol(CFSTR("km/ks"),NULL)); bool result = PSUnitAreEquivalentUnits(unit2, unit2);

would return result = true.

Conversely, while the function PSUnitForSymbol is capable of parsing the input string for any valid SI unit, it is possible that the unit returned is not equivalent to the unit in the parsed string. For example, consider the code below

double unit_multiplier = 1;
PSUnitRef forceUnit = PSUnitForSymbol(CFSTR("dm*m"),&unit_multiplier);

Generally, PhySyUnits always returns a unit using one symbol for each of the seven base dimensions. In this case, returning a unit of either dm^2 or m^2 would not be equivalent to the dm*m. Thus, any number associated with dm*m would need to be modified if it is to be associated with either dm^2 or m^2. Thus, an additional variable unit_multiplier is passed to the function and returned multiplied by the necessary conversion.

Special SI Units

In the SI system there is a set of 22 coherent derived units contained within the set of coherent derived SI units that, for convenience, have their own special names and symbols. The 22 special names and symbols and their corresponding coherent derived units are given below.

Derived Quantity Name Plural Name Symbol Coherent Derived SI Symbol
plane angle radian radians rad (m/m)
solid angle steradian steradians sr (m$^2$/m$^2$)
frequency hertz hertz Hz s$^{-1}$
force newton newtons N m kg s$^{-2}$
pressure, stress pascal pascals Pa m$^{-1}$ kg s$^{-2}$
energy, work, heat joule joules J m$^{2}$ kg s$^{-2}$
power, radiant flux watt watts W m$^{2}$ kg s$^{-1}$
electric charge coulomb coulombs C s A
electric potential difference volt volts V m$^{2}$ kg s$^{-3}$ A$^{-1}$
capacitance farad farads F m$^{-2}$ kg$^{-1}$ s$^{4}$ A$^{2}$
resistance ohm ohms $\Omega$ m$^{2}$ kg s$^{-3}$ A$^{-2}$
electric conductance siemens siemens S m$^{-2}$ kg$^{-1}$ s$^{3}$ A$^{2}$
magnetic flux weber webers Wb m$^{2}$ kg s$^{-2}$ A$^{-1}$
magnetic flux density tesla tesla T kg s$^{-2}$ A$^{-1}$
inductance henry henry H m$^{2}$ kg s$^{-2}$ A$^{-2}$
Celsius Temperature degree Celsius degrees Celsius $^\circ$C K
luminous flux lumen lumens lm (m$^2$/m$^2$) cd
illuminance lux lux lx m$^{-2}$ cd
radionuclide activity becquerel becquerel Bq s$^{-1}$
absorbed dose gray grays Gy m$^{2}$ s$^{-2}$
dose equivalent sievert sieverts Sv m$^{2}$ s$^{-2}$
catalytic activity katal katal kat s$^{-1}$ mol

For example, the coherent derived SI unit: m•kg/s^2, used for the derived quantity of force, is given the special name newton and replaced with the symbol N. Therefore, to include the ability to substitute a special SI unit name and symbol for one of these 22 coherent derived units, PhySyUnits includes three additional attributes of name, plural name, and symbol for derived units that are a special SI unit.

PSUnitRef forceUnit = PSUnitForSymbol(CFSTR("kN"),NULL);

With the introduction of special SI symbols there is the additional possibility of derived quantities with derived units employing a special SI unit. Generally, however, no more than one special SI symbol appears in such a derived unit symbol and the special SI symbol often appears as a linear term in the numerator. For example, the derived quantity dynamic viscosity can use the derived unit Pa•s as well as kg/(m•s). The derived quantity of surface tension can use the derived unit N/m as well as kg/s^2.

Non-SI Units

There are a number of units outside the International System of Units that continue to be used in different science and engineering communities. As mentioned earlier, the physical laws relating physical quantities are independent of any reference unit, and it is only the ratio of measured physical quantities that is physically significant. To handle such Non-SI Units in our data model, the four attributes added for Special SI Units of name, plural name, symbol, and symbol prefix can be employed in the same manner for Non-SI Units. Moreover, a boolean attribute is added to the unit data model to distinguish between Special SI Units and Non-SI units. While the 22 units in the set $[Q]_\text{SU}$ are equivalent to 22 coherent derived units contained within $[Q]_\text{DU}$, this will not be the case with Non-SI units. Thus, the final attribute in our unit data model will be a scaling between the Non-SI root unit and the coherent SI base unit or coherent derived SI unit with the same dimensionality. While derived unit using a non-SI unit will be allowed as input, the quantity (numerical value and unit) will immediate be converted to SI. For example, a quantity created by parsing ``1.0 in/s'' would be stored as 2.54 cm/s.

While it is straightforward to include non-SI unit symbols in the same manner as special SI unit symbols, one has to be careful to avoid symbol and name collisions, particularly as some non-SI unit symbols employ SI prefixes. Derived units employing non-SI symbols would be a more significant challenge. A more versatile approach might be to allow multiple systems of measurement, each with its own set of seven base reference quantities.

double unit_multiplier = 1;
PSUnitRef pressureUnit = PSUnitForSymbol(CFSTR("lb/in^2"),&unit_multiplier);

As with PSDimensionality, PhySy uses a Flyweight design pattern for PSUnit. As such, there is no need for memory management of PSUnit instances. Notice that there is no create nor copy functions. PhySy maintains an internal library of PSUnit instances and, for a given unit, PhySy will always return the same pointer reference. When a unit is requested that doesn't exist in the library, PhySy will create and add that instance to the library. Therefore, there is no need to send a CFRetain or CFRelease to any PSUnit.

See the full API of PSUnit here.

PSDimensionality

Dimensionality

In the SI System seven reference quantities are used to define seven dimensions whose symbols are given below.

Reference Quantity Dimension Symbol
length L
mass M
time T
electric current I
thermodynamic temperature ${\Theta}$
amount of substance N
luminous intensity J

The dimensionality of any physical quantity, $q$, can then be expressed in terms of the seven reference dimensions in the form of a dimensional product \begin{equation} \mbox{dim} \, q = \text{L}^\alpha \text{M}^\beta \text{T}^\gamma \text{I}^\delta{\Theta}^\epsilon \text{N}^\zeta \text{J}^\eta, \end{equation} where the lower case greek symbols represent integers called the dimensional exponents. Dimensionality can also be represented as a point in the space of dimensional exponents $(\alpha, \beta, \gamma, \delta, \epsilon, \zeta, \eta)$. Physical quantities with different meanings can have the same dimensionality. For example, the thermodynamic quantities entropy and heat capacity are different physical quantities having the same physical dimensions. Only physical quantities with the same dimensionality can be added. With the operation of multiplication the physical dimensions form a group.

There also exists dimensionless quantities, such as the angle which has dimensionality of $\text{L}/\text{L}$, or the solid angle with a dimensionality of $\text{L}^2/\text{L}^2$. Representing such dimensionalities requires us to split each dimension exponent into numerator and denominator exponents. Thus, we keep track of these possibilities in PhySy by defining \begin{equation} \mbox{dim} \, q = \left[\frac{\text{L}^{\alpha_+}}{\text{L}^{\alpha_-}} \right] \cdot \left[\frac{\text{M}^{\beta_+}}{\text{M}^{\beta_-}} \right] \cdot \left[\frac{\text{T}^{\gamma_+}}{\text{T}^{\gamma_-}} \right] \cdot \left[\frac{\text{I}^{\delta_+}}{\text{I}^{\delta_-}} \right] \cdot \left[\frac{{\Theta}^{\epsilon_+}}{{\Theta}^{\epsilon_-}} \right] \cdot \left[\frac{\text{N}^{\zeta_+}}{\text{N}^{\zeta_-}} \right] \cdot \left[\frac{\text{J}^{\eta_+}}{\text{J}^{\eta_-}} \right]. \end{equation}

Dimensionalities in PhySy

Dimensionalities in PhySy can be obtained simply with the identifier of the physical quantity. For example,

PSDimensionalityRef force = PSDimensionalityForQuantity(kPSQuantityForce); 
A full list of identifiers can be found here

A dimensionality is displayed in the terminal using

PSDimensionalityShow(force);

which would display

 L•M/T^2

Another way to obtain a dimensionality is using any combination of the symbols for the seven base dimensions, length, mass, time, current, temperature, amount, and luminous intensity, which are L, M, T, I, ϴ, N, and J, respectively. For example,

PSDimensionalityRef acceleration = PSDimensionalityForSymbol(CFSTR("L/T^2")); 

Additionally, one can create a new dimensionality through the mathematical operations of multiplication, division, and exponentiation. For example,

PSDimensionalityRef mass = PSDimensionalityByDividing(force,acceleration); 
PSDimensionalityShow(mass);
would display
M

As an example of multiplication,

PSDimensionalityRef distance = PSDimensionalityForQuantity(kPSQuantityDistance);
PSDimensionalityRef work = PSDimensionalityByMultiplying(force, distance);
PSDimensionalityShow(work);
would display
L^2•M/T^2
and raising to a power,
PSDimensionalityRef area = PSDimensionalityByRaisingToAPower(distance, 2);
PSDimensionalityShow(area);
would display
L^2

In the three functions, PSDimensionalityByMultiplying, PSDimensionalityByDividing, and PSDimensionalityByRaisingToAPower, the numerator and denominator exponents of the returned dimensionality are always reduced to their lowest possible integer values consistent with the dimensionality of the result. If desired, this reduction of numerator and denominator exponents can be avoided with the otherwise identical functions, PSDimensionalityByMultiplyingWithoutReducing, PSDimensionalityByDividingWithoutReducing, and PSDimensionalityByRaisingToAPowerWithoutReducing.

This ability is can be quite useful when dealing with dimensionless quantities. For example, the function call

PSDimensionalityRef angle = PSDimensionalityByDividing(distance,distance);
would result in
PSDimensionalityShow(angle);
displaying
1

Likewise, the function call

PSDimensionalityRef solidAngle = PSDimensionalityByDividing(area,area);
would result in
PSDimensionalityShow(solidAngle);
also displaying
1

Both of these operations returned the same dimensionless dimensionality, i.e., 1. If we instead call

PSDimensionalityRef angleDerived = PSDimensionalityByDividingWithoutReducing(distance,distance);
PSDimensionalityRef solidAngleDerived = PSDimensionalityByDividingWithoutReducing(area,area);
then, calling
PSDimensionalityShow(angleDerived); 
would return
L/L
and calling
PSDimensionalityShow(solidAngleDerived);
would return
L^2/L^2

Thus, while the result of

PSDimensionalityEqual(angle,solidAngle);
is true, the result of
PSDimensionalityEqual(angleDerived,solidAngleDerived);
is false.

On the other hand, the results of both

PSDimensionalityHasSameReducedDimensionality(angle,solidAngle);
and
PSDimensionalityHasSameReducedDimensionality(angleDerived,solidAngleDerived);
are true.

Testing if a dimensionality is dimensionless is accomplished with the function:

PSDimensionalityIsDimensionless(angleDerived);
which, in this context, would return true

PhySy uses a Flyweight design pattern for PSDimensionality. As such, there is no need for memory management of PSDimensionality instances. Notice that there is no create nor copy functions. PhySy maintains an internal library of PSDimensionality instances and, for a given dimensionality, PhySy will always return the same pointer reference. When a dimensionality is requested that doesn't exist in the library, PhySy will create and add that instance to the library. Therefore, there is no need to send a CFRetain or CFRelease to any PSDimensionality.

See the full API of PSDimensionality here.

PSQuantity

PSQuantity represents a physical quantity and has two attributes: a element type and a unit. PSQuantity is an abstract type.

See the full API of PSQuantity here.

PSBlock

PSBlock represents a block of physical quantities. It is a concrete subtype of PSQuantity. It has three essential attributes: a unit, an elementType, and a block of numerical values.

See the full API of PSBlock here.

PhySy Dataset

PSDataset

PSDataset represents signal responses in a multi-dimensional coordinate system. A PSDataset has two essential attributes, an array of PSSignals, each with identical number and type of response values, and an array of PSDimensions, which define each of the dimensions along which the PSSignals are sampled.

See the (poorly documented) API of PSDataset here.

PSSignal

PSSignal represents a block of physical responses. It is a subtype of PSBlock. Like PSBlock, it has three essential attributes: a unit, an elementType, and a block of numerical values. Additionally, a PSSignal can have three optional attributes: an array of coordinates, a response uncertainty, and a name.

A number of PSSignal's advanced methods require on an ordered array of PSDimension types.

See the full API of PSSignal here.

PSDimension

PSDimension describes a uniformly sampled coordinate.

See the full API of PSDimension here.

PSDatum

PSDatum represents a physical response in a coordinate space. It is a subtype of PSScalar. Like PSScalar, it has three essential attributes: a unit, an elementType, and a numerical value. Additionally, a PSDatum, like PSSignal, can have two optional attributes: an array of coordinates, and a response uncertainty.

Two optional transient attributes are memOffset and signalIndex, to indicate the origin for datum derived from a PSDataset

See the full API of PSDatum here.

PSIndexArray

PSIndexArray represents an immutable collection of unique signed integers, known as indexes because of the way they are used. This collection is referred to as an index array.

See the full API of PSIndexArray here.

PSIndexPairSet

PSIndexPairSet represents an immutable collection of unique integer pairs.

The PSIndexPair type is a structure containing two integers: an index and a value; The index can only appear once in the index pair set. A value has no such limitation. The mutable type of PSIndexPairSet is PSMutableIndexSet.

See the full API of PSIndexPairSet here.